#!/usr/bin/env perl
#   Copyright (C) 2021-2025 Free Software Foundation, Inc.
#   Contributed by Oracle.
#
#   This file is part of GNU Binutils.
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 3, or (at your option)
#   any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, 51 Franklin Street - Fifth Floor, Boston,
#   MA 02110-1301, USA.

use strict;
use warnings;

# Disable before release
# use Perl::Critic;

use bigint;
use List::Util qw (max);
use Cwd qw (abs_path cwd);
use File::Basename;
use File::stat;
use feature qw (state);
use POSIX;
use Getopt::Long qw (Configure);

#------------------------------------------------------------------------------
# Check as early as possible if the version of Perl used is supported.
#------------------------------------------------------------------------------
INIT
{
  my $perl_minimal_version_supported = version->parse ("5.10.0")->normal;
  my $perl_current_version           = version->parse ("$]")->normal;

  if ($perl_current_version lt $perl_minimal_version_supported)
    {
      my $msg;

      $msg  = "Error: minimum Perl release required: ";
      $msg .= $perl_minimal_version_supported;
      $msg .= " current: ";
      $msg .= $perl_current_version;
      $msg .= "\n";

      print $msg;

      exit (1);
     }
} #-- End of INIT

#------------------------------------------------------------------------------
# Poor man's version of a boolean.
#------------------------------------------------------------------------------
my $TRUE    = 1;
my $FALSE   = 0;

#------------------------------------------------------------------------------
# The total number of functions to be processed.
#------------------------------------------------------------------------------
my $g_total_function_count = 0;

#------------------------------------------------------------------------------
# Used to ensure correct alignment of columns.
#------------------------------------------------------------------------------
my $g_max_length_first_metric;

#------------------------------------------------------------------------------
# This variable contains the path used to execute $GP_DISPAY_TEXT.
#------------------------------------------------------------------------------
my $g_path_to_tools;

#------------------------------------------------------------------------------
# Code debugging flag.
#------------------------------------------------------------------------------
my $g_test_code = $FALSE;

#------------------------------------------------------------------------------
# GPROFNG commands and files used.
#------------------------------------------------------------------------------
my $GP_DISPLAY_TEXT = "gprofng-display-text";

my $g_gp_output_file   = $GP_DISPLAY_TEXT.".stdout.log";
my $g_gp_error_logfile = $GP_DISPLAY_TEXT.".stderr.log";

#------------------------------------------------------------------------------
# Global variables.
#------------------------------------------------------------------------------
my $g_addressing_mode = "64 bit";

#------------------------------------------------------------------------------
# The global regex section.
#
# First step towards consolidating all regexes.
#------------------------------------------------------------------------------
  my $g_less_than_regex      = '<';
  my $g_html_less_than_regex = '&lt;';
  my $g_endbr_inst_regex     = 'endbr[32|64]';
  my $g_rm_surrounding_spaces_regex = '^\s+|\s+$';

#------------------------------------------------------------------------------
# For consistency, use a global variable.
#------------------------------------------------------------------------------
  my $g_html_new_line = "<br>";

#------------------------------------------------------------------------------
# These are the regex's used.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Disassembly analysis
#------------------------------------------------------------------------------
  my $g_branch_regex = '\.*([0-9a-fA-F]*):\s+(j).*\s*0x([0-9a-fA-F]+)';
  my $g_endbr_regex  = '\.*([0-9a-fA-F]*):\s+(endbr[32|64])';
  my $g_function_call_v2_regex =
		'(.*)\s+([0-9a-fA-F]*):\s+(call)\s*0x([0-9a-fA-F]+)\s*';

my $g_first_metric;

my $binutils_version;
my $driver_cmd;
my $tool_name;
my $version_info;

my %g_mapped_cmds = ();

#------------------------------------------------------------------------------
# Variables dealing with warnings and errors.  Since a message may span
# multiple lines (for readability reasons), the number of entries in the
# array may not reflect the total number of messages.  This is why we use
# separate variables for the counts.
#------------------------------------------------------------------------------
my @g_error_msgs   = ();
my @g_warning_msgs = ();
my $g_total_error_count = 0;
#------------------------------------------------------------------------------
# This count is used in the html_create_warnings_page HTML page to show how
# many warning messages there are.  Warnings are printed through gp_message(),
# but since one warning may span multiple lines, we update a separate counter
# that contains the total number of warning messages issued so far.
#------------------------------------------------------------------------------
my $g_total_warning_count = 0;
my $g_options_printed     = $FALSE;
my $g_abort_msg = "cannot recover from the error(s)";

#------------------------------------------------------------------------------
# Contains the names that have already been tagged.  This is a global
# structure because otherwise the code would get much more complicated.
#------------------------------------------------------------------------------
my %g_tagged_names = ();

#------------------------------------------------------------------------------
# TBD Remove the use of these structures. No longer used.
#------------------------------------------------------------------------------
my %g_function_tag_id = ();
my $g_context = 5; # Defines the range of scan

my $g_default_setting_lang = "en-US.UTF-8";
my %g_exp_dir_meta_data;

my $g_html_credits_line;

my $g_warn_keyword  = "[Warning]";
my $g_error_keyword = "[Error]";

my %g_function_occurrences = ();
my %g_map_function_to_index = ();
my %g_multi_count_function = ();
my %g_function_view_all = ();
my @g_full_function_view_table = ();

my @g_html_experiment_stats = ();

#------------------------------------------------------------------------------
# These structures contain the information printed in the function views.
#------------------------------------------------------------------------------
my $g_header_lines;

my @g_html_function_name = ();

#------------------------------------------------------------------------------
# TBD: This variable may not be needed and replaced by tp_value
my $thresh = 0;
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Define the driver command, tool name and version number.
#------------------------------------------------------------------------------
$driver_cmd       = "gprofng display html";
$tool_name        = "gprofng-display-html";
#$binutils_version = "2.38.50";
$binutils_version = "2.43.0";
$version_info     = $tool_name . " GNU binutils version " . $binutils_version;

#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Define several key data structures.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# This table has the settings of the variables the user may set.
#------------------------------------------------------------------------------
my %g_user_settings =
  (
    verbose              => { option => "--verbose",
			      no_of_arguments => 1,
			      data_type => "onoff",
			      current_value => "off",  defined => $FALSE},

    debug                => { option => "--debug",
			      no_of_arguments => 1,
			      data_type => "size",
			      current_value => "off",  defined => $FALSE},

    warnings             => { option => "--warnings",
			      no_of_arguments => 1,
			      data_type => "onoff" ,
			      current_value => "off",  defined => $FALSE},

    nowarnings           => { option => "--nowarnings",
			      no_of_arguments => 1,
			      data_type => "onoff",
			      current_value => "off",  defined => $FALSE},

    quiet                => { option => "--quiet",
			      no_of_arguments => 1,
			      data_type => "onoff",
			      current_value => "off",  defined => $FALSE},

    output               => { option => "-o",
			      no_of_arguments => 1,
			      data_type => "path",
			      current_value => undef,  defined => $FALSE},

    overwrite            => { option => "-O",
			      no_of_arguments => 1,
			      data_type => "path",
			      current_value => undef,  defined => $FALSE},

    calltree             => { option => "-ct",
			      no_of_arguments => 1,
			      data_type => "onoff",
			      current_value => "off",  defined => $FALSE},

    func_limit           => { option => "-fl",
			      no_of_arguments => 1,
			      data_type => "pinteger",
			      current_value => 500,    defined => $FALSE},

    highlight_percentage => { option => "--highlight-percentage",
			      no_of_arguments => 1,
			      data_type => "pfloat",
			      current_value   => 90.0, defined => $FALSE},

    hp                   => { option => "-hp",
			      no_of_arguments => 1,
			      data_type => "pfloat",
			      current_value => 90.0,   defined => $FALSE},

    threshold_percentage => { option => "-tp",
			      no_of_arguments => 1,
			      data_type => "pfloat",
			      current_value => 100.0,  defined => $FALSE},

    default_metrics      => { option => "-dm",
			      no_of_arguments => 1,
			      data_type => "onoff",
			      current_value => "off",  defined => $FALSE},

    ignore_metrics       => { option => "-im",
			      no_of_arguments => 1,
			      data_type => "metric_names",
			      current_value => undef,  defined => $FALSE},
  );

#------------------------------------------------------------------------------
# Convenience.  These map the on/off value to $TRUE/$FALSE to make the code
# easier to read.  For example: "if ($g_verbose)" as opposed to the following:
# "if ($verbose_setting eq "on").
#------------------------------------------------------------------------------
my $g_verbose  = $FALSE;
my $g_debug    = $FALSE;
my $g_warnings = $TRUE;
my $g_quiet    = $FALSE;

#------------------------------------------------------------------------------
# Since ARGV is modified when parsing the options, a clean copy is used to
# print the original ARGV values in case of a warning, or error.
#------------------------------------------------------------------------------
my @CopyOfARGV = ();

my %g_debug_size =
  (
    "on"  => $FALSE,
    "s"   => $FALSE,
    "m"   => $FALSE,
    "l"   => $FALSE,
    "xl"  => $FALSE,
  );

my %local_system_config =
  (
    kernel_name       => "undefined",
    nodename          => "undefined",
    kernel_release    => "undefined",
    kernel_version    => "undefined",
    machine           => "undefined",
    processor         => "undefined",
    hardware_platform => "undefined",
    operating_system  => "undefined",
    hostname_current  => "undefined",
  );

#------------------------------------------------------------------------------
# Note that we use single quotes here, because regular expressions wreak
# havoc otherwise.
#------------------------------------------------------------------------------

my %g_arch_specific_settings =
  (
    arch_supported  => $FALSE,
    arch            => 'undefined',
    regex           => 'undefined',
    subexp          => 'undefined',
    linksubexp      => 'undefined',
  );

my %g_locale_settings = (
  LANG              => "en_US.UTF-8",
  decimal_separator => "\\.",
  covert_to_dot     => $FALSE
);

#------------------------------------------------------------------------------
# See this page for a nice overview with the colors:
# https://www.w3schools.com/colors/colors_groups.asp
#------------------------------------------------------------------------------

my %g_html_color_scheme = (
  "control_flow"  => "Brown",
  "target_function_name" => "Red",
  "non_target_function_name" => "BlueViolet",
  "background_color_hot" => "PeachPuff",
  "background_color_lukewarm" => "LemonChiffon",
  "link_outside_range" => "Crimson",
  "error_message" => "LightPink",
  "background_color_page" => "White",
#  "background_color_page" => "LightGray",
  "background_selected_sort" => "LightSlateGray",
  "index" => "Lavender",
);

#------------------------------------------------------------------------------
# These are the base names for the HTML files that are generated.
#------------------------------------------------------------------------------
my %g_html_base_file_name = (
  "caller_callee"  => "caller-callee",
  "disassembly" => "dis",
  "experiment_info"  => "experiment-info",
  "function_view"  => "function-view-sorted",
  "index" => "index",
  "source" => "src",
  "warnings" => "warnings",
);

#------------------------------------------------------------------------------
# Introducing main() is cosmetic, but helps with the scoping of variables.
#------------------------------------------------------------------------------
  main ();

  exit (0);

#------------------------------------------------------------------------------
# This is the driver part of the program.
#------------------------------------------------------------------------------
sub main
{
  my $subr_name = get_my_name ();

  @CopyOfARGV = @ARGV;

#------------------------------------------------------------------------------
# The name of the configuration file.
#------------------------------------------------------------------------------
  my $rc_file_name = ".gp-display-html.rc";

#------------------------------------------------------------------------------
# OS commands executed and search paths.
#
# TBD: check if elfdump should be here too (most likely not though)
#------------------------------------------------------------------------------
  my @selected_os_cmds = qw (rm cat hostname locale which printenv uname
			     readelf mkdir);

  my @search_paths_os_cmds = qw (
    /usr/bin
    /bin
    /usr/local/bin
    /usr/local/sbin
    /usr/sbin
    /sbin
  );

#------------------------------------------------------------------------------
# TBD: Eliminate these.
#------------------------------------------------------------------------------
  my $ARCHIVES_MAP_NAME;
  my $ARCHIVES_MAP_VADDR;

#------------------------------------------------------------------------------
# Local structures (hashes and arrays).
#------------------------------------------------------------------------------
  my @exp_dir_list = ();
  my @metrics_data;

  my %function_address_info = ();
  my $function_address_info_ref;

  my @function_info = ();
  my $function_info_ref;

  my %function_address_and_index = ();
  my $function_address_and_index_ref;

  my %addressobjtextm = ();
  my $addressobjtextm_ref;

  my %addressobj_index = ();
  my $addressobj_index_ref;

  my %LINUX_vDSO = ();
  my $LINUX_vDSO_ref;

  my %function_view_structure = ();
  my $function_view_structure_ref;

  my %elf_rats = ();
  my $elf_rats_ref;

#------------------------------------------------------------------------------
# Local variables.
#------------------------------------------------------------------------------
  my $abs_path_outputdir;
  my $archive_dir_not_empty;
  my $base_va_executable;
  my $executable_name;
  my $found_exp_dir;
  my $ignore_value;
  my $msg;
  my $number_of_metrics;
  my $va_executable_in_hex;

  my $failed_command_mappings;

  my $script_pc_metrics;
  my $dir_check_errors;
  my $consistency_errors;
  my $outputdir;
  my $return_code;

  my $decimal_separator;
  my $convert_to_dot;
  my $architecture_supported;
  my $elf_arch;
  my $elf_support;
  my $home_dir;
  my $elf_loadobjects_found;

  my $rc_file_paths_ref;
  my @rc_file_paths = ();
  my $rc_file_errors = 0;

  my @sort_fields = ();
  my $summary_metrics;
  my $call_metrics;
  my $user_metrics;
  my $system_metrics;
  my $wall_metrics;
  my $detail_metrics;
  my $detail_metrics_system;

  my $html_test;
  my @experiment_data;
  my $exp_info_file;
  my $exp_info_ref;
  my @exp_info;

  my $pretty_dir_list;

  my %metric_value       = ();
  my %metric_description = ();
  my %metric_description_reversed = ();
  my %metric_found = ();
  my %ignored_metrics = ();

  my $metric_value_ref;
  my $metric_description_ref;
  my $metric_found_ref;
  my $ignored_metrics_ref;

  my @table_execution_stats = ();
  my $table_execution_stats_ref;

  my $html_first_metric_file_ref;
  my $html_first_metric_file;

  my $arch;
  my $subexp;
  my $linksubexp;

  my $setting_for_LANG;
  my $time_percentage_multiplier;
  my $process_all_functions;

  my $selected_archive;

#------------------------------------------------------------------------------
# If no options are given, print the help info and exit.
#------------------------------------------------------------------------------
  if ($#ARGV == -1)
    {
      $ignore_value = print_help_info ();
      return (0);
    }

#------------------------------------------------------------------------------
# This part is like a preamble.  Before we continue we need to figure out some
# things that are needed later on.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Store the absolute path of the command executed.
#------------------------------------------------------------------------------
  my $location_gp_command = $0;

#------------------------------------------------------------------------------
# Get the ball rolling. Parse and interpret the options.  Some first checks
# are performed.
#
# Instead of bailing out on the first user error, we capture all warnings and
# errors.  The warnings, if any, will be printed once the command line has
# been parsed and verified.  Execution continues.
#
# Any error(s) accumulated in this phase will be printed after the command
# line has been parsed and verified.  Execution is then terminated.
#
# In the remainder, any error encountered will immediately terminate the
# execution because we can't guarantee the remaining code will work up to
# some point.
#------------------------------------------------------------------------------
  my ($found_exp_dir_ref, $exp_dir_list_ref) = parse_and_check_user_options ();

  $found_exp_dir = ${ $found_exp_dir_ref };

  if ($found_exp_dir)
    {
      @exp_dir_list = @{ $exp_dir_list_ref };
    }
  else
    {
      $msg = "the list with experiments is either missing, or incorrect";
      gp_message ("debug", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# The final settings for verbose, debug, warnings and quiet are known and the
# gp_message() subroutine is aware of these.
#------------------------------------------------------------------------------
  $msg = "parsing of the user options completed";
  gp_message ("verbose", $subr_name, $msg);

#------------------------------------------------------------------------------
# The user options have been taken in.  Check for validity and consistency.
#------------------------------------------------------------------------------
  $msg = "process user options";
  gp_message ("verbose", $subr_name, $msg);

  ($ignored_metrics_ref, $outputdir,
   $time_percentage_multiplier, $process_all_functions, $exp_dir_list_ref) =
					process_user_options (\@exp_dir_list);

  @exp_dir_list    = @{ $exp_dir_list_ref };
  %ignored_metrics = %{$ignored_metrics_ref};

#------------------------------------------------------------------------------
# The next subroutine is executed early to ensure the OS commands we need are
# available.
#
# This subroutine stores the commands and the full path names as an
# associative array called "g_mapped_cmds".  The command is the key and the
# value is the full path.  For example: ("uname", /usr/bin/uname).
#------------------------------------------------------------------------------
  gp_message ("debug", $subr_name, "verify the OS commands");
  $failed_command_mappings = check_and_define_cmds (\@selected_os_cmds,
						    \@search_paths_os_cmds);

  if ($failed_command_mappings == 0)
    {
      $msg = "successfully verified the OS commands";
      gp_message ("debug", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Time to check if any warnings and/or errors have been generated.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# We have completed all the upfront checks.  Print any warnings and errors.
# If there are already any errors, execution is terminated.  As execution
# continues, errors may occur and they are typically fatal.
#------------------------------------------------------------------------------
  if ($g_debug)
    {
      $msg = "internal settings after option processing";
      $ignore_value = print_table_user_settings ("diag", $msg);
    }

#------------------------------------------------------------------------------
# Terminate execution in case fatal errors have occurred.
#------------------------------------------------------------------------------
  if ( $g_total_error_count > 0)
    {
      my $msg = "the current values for the user controllable settings";
      print_user_settings ("debug", $msg);

      gp_message ("abort", $subr_name, $g_abort_msg);
    }
  else
    {
      my $msg = "after parsing the user options, the final values are";
      print_user_settings ("debug", $msg);
    }

#------------------------------------------------------------------------------
# If no option is given for the output directory, pick a default.  Otherwise,
# if the output directory exists, wipe it clean in case the -O option is used.
# If not, raise an error because the -o option does not overwrite an existing
# directory.
# Also in case of other errors, the execution is terminated.
#------------------------------------------------------------------------------
  $outputdir = set_up_output_directory ();
  $abs_path_outputdir = Cwd::cwd () . "/" . $outputdir;

  $msg = "the output directory is $outputdir";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Get the home directory and the locations for the configuration file on the
# current system.
#------------------------------------------------------------------------------
  ($home_dir, $rc_file_paths_ref) = get_home_dir_and_rc_path ($rc_file_name);

  @rc_file_paths = @{ $rc_file_paths_ref };

  $msg = "the home directory is $home_dir";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# TBD: de-activated until this feature has been fully implemented.
#------------------------------------------------------------------------------
##  $msg =  "the search path for the rc file is @rc_file_paths";
##  gp_message ("debug", $subr_name, $msg);
##  $pretty_dir_list = build_pretty_dir_list (\@rc_file_paths);

#------------------------------------------------------------------------------
# Get the ball rolling.  Parse and interpret the configuration file (if any)
# and the command line options.
#
# Note that the verbose, debug, and quiet options can be set in this file.
# It is a deliberate choice to ignore these for now.  The assumption is that
# the user will not be happy if we ignore the command line settings for a
# while.
#------------------------------------------------------------------------------
  $msg = "processing of the rc file has been disabled for now";
  gp_message ("debugXL", $subr_name, $msg);

# Temporarily disabled
# print_table_user_settings ("debugXL", "before function process_rc_file");
# $rc_file_errors = process_rc_file ($rc_file_name, $rc_file_paths_ref);
# if ($rc_file_errors != 0)
# {
#   $message = "fatal errors in file $rc_file_name encountered";
#   gp_message ("debugXL", $subr_name, $message);
# }
# print_table_user_settings ("debugXL", "after function process_rc_file");

#------------------------------------------------------------------------------
# Print a list with the experiment directory names
#------------------------------------------------------------------------------
  $pretty_dir_list = build_pretty_dir_list (\@exp_dir_list);

  my $plural = ($#exp_dir_list > 0) ? "directories are" : "directory is";

  $msg = "the experiment " . $plural . ":";
  gp_message ("verbose", $subr_name, $msg);
  gp_message ("verbose", $subr_name, $pretty_dir_list);

#------------------------------------------------------------------------------
# Set up the first entry with the meta data for the experiments.  This field
# contains the absolute paths to the experiment directories.
#------------------------------------------------------------------------------
  for my $exp_dir (@exp_dir_list)
    {
     my ($filename, $directory_path, $ignore_suffix) = fileparse ($exp_dir);
     gp_message ("debug", $subr_name, "exp_dir = $exp_dir");
     gp_message ("debug", $subr_name, "filename = $filename");
     gp_message ("debug", $subr_name, "directory_path = $directory_path");
     $g_exp_dir_meta_data{$filename}{"directory_path"} = $directory_path;
    }

#------------------------------------------------------------------------------
# TBD:
# This subroutine may be overkill.  See what is really needed here and remove
# everything else.
#
# Upon return, one directory has been selected to be used in the remainder.
# This is not always the correct thing to do, but is the same as the original
# code.  In due time this should be addressed though.
#------------------------------------------------------------------------------
  ($archive_dir_not_empty, $selected_archive, $elf_rats_ref) =
				check_validity_exp_dirs (\@exp_dir_list);

  %elf_rats = %{$elf_rats_ref};

  $msg = "the experiment directories have been verified and are valid";
  gp_message ("verbose", $subr_name, $msg);

#------------------------------------------------------------------------------
# Now that we know the map.xml file(s) are present, we can scan these and get
# the required information.  This includes setting the base virtual address.
#------------------------------------------------------------------------------
  $ignore_value = determine_base_virtual_address ($exp_dir_list_ref);

#------------------------------------------------------------------------------
# Check whether the experiment directories are consistent.
#------------------------------------------------------------------------------
  ($consistency_errors, $executable_name) =
			verify_consistency_experiments ($exp_dir_list_ref);

  if ($consistency_errors == 0)
    {
      $msg = "the experiment directories are consistent";
      gp_message ("verbose", $subr_name, $msg);
    }
  else
    {
      $msg  = "the number of consistency errors detected: $consistency_errors";
      gp_message ("abort", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# The directories are consistent.  We can now set the base virtual address of
# the executable.
#------------------------------------------------------------------------------
  $base_va_executable =
		$g_exp_dir_meta_data{$selected_archive}{"va_base_in_hex"};

  $msg = "executable_name    = " . $executable_name;
  gp_message ("debug", $subr_name, $msg);
  $msg = "selected_archive   = " . $selected_archive;
  gp_message ("debug", $subr_name, $msg);
  $msg = "base_va_executable = " . $base_va_executable;
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# The $GP_DISPLAY_TEXT tool is critical and has to be available in order to
# proceed.
# This subroutine only returns a value if the tool can be found.
#------------------------------------------------------------------------------
  $g_path_to_tools = ${ check_availability_tool (\$location_gp_command)};

  $GP_DISPLAY_TEXT = $g_path_to_tools . $GP_DISPLAY_TEXT;

  $msg = "updated GP_DISPLAY_TEXT = $GP_DISPLAY_TEXT";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Check if $GP_DISPLAY_TEXT is executable for user, group, and other.
# If not, print a warning only, since this may not be fatal but could
# potentially lead to issues later on.
#------------------------------------------------------------------------------
  if (not is_file_executable ($GP_DISPLAY_TEXT))
    {
      $msg  = "file $GP_DISPLAY_TEXT is not executable for user, group, and";
      $msg .= " other";
      gp_message ("warning", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Find out what the decimal separator is, as set by the user.
#------------------------------------------------------------------------------
  ($return_code, $decimal_separator, $convert_to_dot) =
                                                determine_decimal_separator ();

  if ($return_code == 0)
    {
      $msg  = "decimal separator is $decimal_separator";
      $msg .= " (conversion to dot is ";
      $msg .= ($convert_to_dot == $TRUE ? "enabled" : "disabled") . ")";
      gp_message ("debugXL", $subr_name, $msg);
    }
  else
    {
      $msg  = "the decimal separator cannot be determined -";
      $msg .= " set to $decimal_separator";
      gp_message ("warning", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Collect and store the system information.
#------------------------------------------------------------------------------
  $msg = "collect system information and adapt settings";
  gp_message ("verbose", $subr_name, $msg);

  $return_code = get_system_config_info ();

#------------------------------------------------------------------------------
# The 3 variables below are used in the remainder.
#
# The output from "uname -p" is recommended to be used for the ISA.
#------------------------------------------------------------------------------
  my $hostname_current = $local_system_config{hostname_current};
  my $arch_uname_s     = $local_system_config{kernel_name};
  my $arch_uname       = $local_system_config{processor};

  gp_message ("debug", $subr_name, "set hostname_current = $hostname_current");
  gp_message ("debug", $subr_name, "set arch_uname_s     = $arch_uname_s");
  gp_message ("debug", $subr_name, "set arch_uname       = $arch_uname");

#------------------------------------------------------------------------------
# This function also sets the values in "g_arch_specific_settings".  This
# includes several definitions of regular expressions.
#------------------------------------------------------------------------------
  ($architecture_supported, $elf_arch, $elf_support) =
		set_system_specific_variables ($arch_uname, $arch_uname_s);

  $msg = "architecture_supported = $architecture_supported";
  gp_message ("debug", $subr_name, $msg);
  $msg = "elf_arch               = $elf_arch";
  gp_message ("debug", $subr_name, $msg);
  $msg = "elf_support            = ".($elf_arch ? "TRUE" : "FALSE");
  gp_message ("debug", $subr_name, $msg);

  for my $feature (sort keys %g_arch_specific_settings)
    {
      $msg  = "g_arch_specific_settings{$feature} = ";
      $msg .= $g_arch_specific_settings{$feature};
      gp_message ("debug", $subr_name, $msg);
    }

  $arch       = $g_arch_specific_settings{"arch"};
  $subexp     = $g_arch_specific_settings{"subexp"};
  $linksubexp = $g_arch_specific_settings{"linksubexp"};

  $g_locale_settings{"LANG"} =  get_LANG_setting ();

  $msg = "after get_LANG_setting: LANG = $g_locale_settings{'LANG'}";
  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Temporarily reset selected settings since these are not yet implemented.
#------------------------------------------------------------------------------
  $ignore_value = reset_selected_settings ();

#------------------------------------------------------------------------------
# TBD: Revisit. Is this really necessary?
#------------------------------------------------------------------------------

  ($executable_name, $va_executable_in_hex) =
				check_loadobjects_are_elf ($selected_archive);
  $elf_loadobjects_found = $TRUE;

# TBD: Hack and those ARCHIVES_ names can be eliminated
  $ARCHIVES_MAP_NAME  = $executable_name;
  $ARCHIVES_MAP_VADDR = $va_executable_in_hex;

  $msg = "hack ARCHIVES_MAP_NAME  = $ARCHIVES_MAP_NAME";
  gp_message ("debugXL", $subr_name, $msg);
  $msg = "hack ARCHIVES_MAP_VADDR = $ARCHIVES_MAP_VADDR";
  gp_message ("debugXL", $subr_name, $msg);

  $msg  = "after call to check_loadobjects_are_elf forced";
  $msg .= " elf_loadobjects_found = $elf_loadobjects_found";
  gp_message ("debugXL", $subr_name, $msg);

  $g_html_credits_line = ${ create_html_credits () };

  $msg = "g_html_credits_line = $g_html_credits_line";
  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Add a "/" to simplify the construction of path names in the remainder.
#
# TBD: Push this into a subroutine(s).
#------------------------------------------------------------------------------
  $outputdir = append_forward_slash ($outputdir);

  $msg = "prepared outputdir = ". $outputdir;
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# ******* TBD: e.system not available on Linux!!
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

##  my $summary_metrics       = 'e.totalcpu';
  $detail_metrics        = 'e.totalcpu';
  $detail_metrics_system = 'e.totalcpu:e.system';
  $call_metrics          = 'a.totalcpu';

  $msg = "set detail_metrics_system = " . $detail_metrics_system;
  gp_message ("debug", $subr_name, $msg);
  $msg = "set detail_metrics        = " . $detail_metrics;
  gp_message ("debug", $subr_name, $msg);
  $msg = "set call_metrics          = " . $call_metrics;
  gp_message ("debug", $subr_name, $msg);

  my $cmd_options;
  my $metrics_cmd;

  my $outfile1      = $outputdir   ."metrics";
  my $outfile2      = $outputdir . "metrictotals";
  my $gp_error_file = $outputdir . $g_gp_error_logfile;

#------------------------------------------------------------------------------
# Execute the $GP_DISPLAY_TEXT tool with the appropriate options.  The goal is
# to get all the output in files $outfile1 and $outfile2.  These are then
# parsed.
#------------------------------------------------------------------------------
  $msg = "gather the metrics data from the experiments";
  gp_message ("verbose", $subr_name, $msg);

  $return_code = get_metrics_data (\@exp_dir_list, $outputdir, $outfile1,
				   $outfile2, $gp_error_file);

  if ($return_code != 0)
    {
      gp_message ("abort", $subr_name, "execution terminated");
    }

#------------------------------------------------------------------------------
# TBD: Test this code
#------------------------------------------------------------------------------
  $msg = "unable to open metric value data file $outfile1 for reading:";
  open (METRICS, "<", $outfile1)
    or die ($subr_name . " - " . $msg . " " . $!);

  $msg = "opened file $outfile1 for reading";
  gp_message ("debug", $subr_name, "opened file $outfile1 for reading");

  chomp (@metrics_data = <METRICS>);
  close (METRICS);

  for my $i (keys @metrics_data)
    {
      $msg = "metrics_data[$i] = " . $metrics_data[$i];
      gp_message ("debugXL", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Process the generated metrics data.
#------------------------------------------------------------------------------
  if ($g_user_settings{"default_metrics"}{"current_value"} eq "off")

#------------------------------------------------------------------------------
# The metrics will be derived from the experiments.
#------------------------------------------------------------------------------
    {
      gp_message ("verbose", $subr_name, "Process the metrics data");

      ($metric_value_ref, $metric_description_ref, $metric_found_ref,
       $user_metrics, $system_metrics, $wall_metrics,
       $summary_metrics, $detail_metrics, $detail_metrics_system, $call_metrics
       ) = process_metrics_data ($outfile1, $outfile2, \%ignored_metrics);

      %metric_value                = %{ $metric_value_ref };
      %metric_description          = %{ $metric_description_ref };
      %metric_found                = %{ $metric_found_ref };
      %metric_description_reversed = reverse %metric_description;

      $msg = "after the call to process_metrics_data";
      gp_message ("debugXL", $subr_name, $msg);

      for my $metric (sort keys %metric_value)
        {
          $msg = "metric_value{$metric} = " . $metric_value{$metric};
          gp_message ("debugXL", $subr_name, $msg);
        }
      for my $metric (sort keys %metric_description)
        {
          $msg  = "metric_description{$metric} =";
          $msg .= " " . $metric_description{$metric};
          gp_message ("debugXL", $subr_name, $msg);
        }
      gp_message ("debugXL", $subr_name, "user_metrics   = $user_metrics");
      gp_message ("debugXL", $subr_name, "system_metrics = $system_metrics");
      gp_message ("debugXL", $subr_name, "wall_metrics   = $wall_metrics");
    }
  else
    {
#------------------------------------------------------------------------------
# A default set of metrics will be used.
#
# TBD: These should be OS dependent.
#------------------------------------------------------------------------------
      $msg = "select the set of default metrics";
      gp_message ("verbose", $subr_name, $msg);

      ($metric_description_ref, $metric_found_ref, $summary_metrics,
       $detail_metrics, $detail_metrics_system, $call_metrics
       ) = set_default_metrics ($outfile1, \%ignored_metrics);


      %metric_description          = %{ $metric_description_ref };
      %metric_found                = %{ $metric_found_ref };
      %metric_description_reversed = reverse %metric_description;

      $msg = "after the call to set_default_metrics";
      gp_message ("debug", $subr_name, $msg);

    }

  $number_of_metrics = split (":", $summary_metrics);

  $msg = "summary_metrics       = " . $summary_metrics;
  gp_message ("debugM", $subr_name, $msg);
  $msg = "detail_metrics        = " . $detail_metrics;
  gp_message ("debugM", $subr_name, $msg);
  $msg = "detail_metrics_system = " . $detail_metrics_system;
  gp_message ("debugM", $subr_name, $msg);
  $msg = "call_metrics          = " . $call_metrics;
  gp_message ("debugM", $subr_name, $msg);
  $msg = "number_of_metrics     = " . $number_of_metrics;
  gp_message ("debugM", $subr_name, $msg);

#------------------------------------------------------------------------------
# TBD Find a way to better handle this situation:
#------------------------------------------------------------------------------
  for my $im (keys %metric_found)
    {
      $msg = "metric_found{$im} = " . $metric_found{$im};
      gp_message ("debugXL", $subr_name, $msg);
    }
  for my $im (keys %ignored_metrics)
    {
      if (not exists ($metric_found{$im}))
        {
          $msg  = "user requested ignored metric (-im) $im does not exist in";
          $msg .= " collected metrics";
          gp_message ("debugXL", $subr_name, $msg);
        }
    }

#------------------------------------------------------------------------------
# Get the information on the experiments.
#------------------------------------------------------------------------------
  $msg = "generate the experiment information";
  gp_message ("verbose", $subr_name, $msg);

  my $experiment_data_ref = get_experiment_info (\$outputdir, \@exp_dir_list);
  @experiment_data = @{ $experiment_data_ref };

  for my $i (sort keys @experiment_data)
    {
      my $msg = "i = $i " . $experiment_data[$i]{"exp_id"} . " => " .
                $experiment_data[$i]{"exp_name_full"};
      gp_message ("debugM", $subr_name, $msg);
    }

  $experiment_data_ref = process_experiment_info ($experiment_data_ref);
  @experiment_data = @{ $experiment_data_ref };

  for my $i (sort keys @experiment_data)
    {
      for my $fields (sort keys %{ $experiment_data[$i] })
        {
          my $msg = "i = $i experiment_data[$i]{$fields} = " .
                    $experiment_data[$i]{$fields};
          gp_message ("debugXL", $subr_name, $msg);
        }
    }

  @g_html_experiment_stats = @{ create_exp_info (\@exp_dir_list,
						 \@experiment_data) };

  $table_execution_stats_ref = html_generate_exp_summary (\$outputdir,
							  \@experiment_data);
  @table_execution_stats = @{ $table_execution_stats_ref };

#------------------------------------------------------------------------------
# Get the function overview.
#------------------------------------------------------------------------------
  $msg = "generate the list with functions executed";
  gp_message ("verbose", $subr_name, $msg);

  my ($outfile, $sort_fields_ref) =
	      get_hot_functions (\@exp_dir_list, $summary_metrics, $outputdir);

  @sort_fields = @{$sort_fields_ref};

#------------------------------------------------------------------------------
# Parse the output from the fsummary command and store the relevant data for
# all the functions listed there.
#------------------------------------------------------------------------------
  $msg = "analyze and store the relevant function information";
  gp_message ("verbose", $subr_name, $msg);

  ($function_info_ref, $function_address_and_index_ref, $addressobjtextm_ref,
   $LINUX_vDSO_ref, $function_view_structure_ref) =
						get_function_info ($outfile);

  @function_info              = @{ $function_info_ref };
  %function_address_and_index = %{ $function_address_and_index_ref };
  %addressobjtextm            = %{ $addressobjtextm_ref };
  %LINUX_vDSO                 = %{ $LINUX_vDSO_ref };
  %function_view_structure    = %{ $function_view_structure_ref };

  $msg = "found " . $g_total_function_count . " functions to process";
  gp_message ("verbose", $subr_name, $msg);

  for my $keys (0 .. $#function_info)
    {
      for my $fields (keys %{$function_info[$keys]})
        {
          $msg = "$keys $fields $function_info[$keys]{$fields}";
          gp_message ("debugXL", $subr_name, $msg);
        }
    }

  for my $i (keys %addressobjtextm)
    {
      $msg = "addressobjtextm{$i} = " . $addressobjtextm{$i};
      gp_message ("debugXL", $subr_name, $msg);
    }

  $msg  = "generate the files with function overviews and the";
  $msg .= " callers-callees information";
  gp_message ("verbose", $subr_name, $msg);

  $script_pc_metrics = generate_function_level_info (\@exp_dir_list,
                                                     $call_metrics,
                                                     $summary_metrics,
                                                     $outputdir,
                                                     $sort_fields_ref);

  $msg = "preprocess the files with the function level information";
  gp_message ("verbose", $subr_name, $msg);

  $ignore_value = preprocess_function_files (
                    $metric_description_ref,
                    $script_pc_metrics,
                    $outputdir,
                    \@sort_fields);

  $msg = "for each function, generate a set of files";
  gp_message ("verbose", $subr_name, $msg);

  ($function_info_ref, $function_address_info_ref, $addressobj_index_ref) =
			process_function_files (\@exp_dir_list,
						$executable_name,
						$time_percentage_multiplier,
						$summary_metrics,
						$process_all_functions,
						$elf_loadobjects_found,
						$outputdir,
						\@sort_fields,
						\@function_info,
						\%function_address_and_index,
						\%LINUX_vDSO,
						\%metric_description,
						$elf_arch,
						$base_va_executable,
						$ARCHIVES_MAP_NAME,
						$ARCHIVES_MAP_VADDR,
						\%elf_rats);

  @function_info         = @{ $function_info_ref };
  %function_address_info = %{ $function_address_info_ref };
  %addressobj_index      = %{ $addressobj_index_ref };

#------------------------------------------------------------------------------
# Parse the disassembly information and generate the html files.
#------------------------------------------------------------------------------
  $msg = "parse the disassembly files and generate the html files";
  gp_message ("verbose", $subr_name, $msg);

  $ignore_value = parse_dis_files (\$number_of_metrics,
				  \@function_info,
				  \%function_address_and_index,
				  \$outputdir,
				  \%addressobj_index);

#------------------------------------------------------------------------------
# Parse the source information and generate the html files.
#------------------------------------------------------------------------------
  $msg = "parse the source files and generate the html files";
  gp_message ("verbose", $subr_name, $msg);

  parse_source_files (\$number_of_metrics, \@function_info, \$outputdir);

#------------------------------------------------------------------------------
# Parse the caller-callee information and generate the html files.
#------------------------------------------------------------------------------
  $msg = "process the caller-callee information and generate the html file";
  gp_message ("verbose", $subr_name, $msg);

#------------------------------------------------------------------------------
# Generate the caller-callee information.
#------------------------------------------------------------------------------
  $ignore_value = generate_caller_callee (\$number_of_metrics,
					  \@function_info,
					  \%function_view_structure,
					  \%function_address_info,
					  \%addressobjtextm,
					  \$outputdir);

#------------------------------------------------------------------------------
# Parse the calltree information and generate the html files.
#------------------------------------------------------------------------------
  if ($g_user_settings{"calltree"}{"current_value"} eq "on")
    {
      $msg = "process the call tree information and generate the html file";
      gp_message ("verbose", $subr_name, $msg);

      $ignore_value = process_calltree (\@function_info,
					\%function_address_info,
					\%addressobjtextm,
					$outputdir);
    }

#------------------------------------------------------------------------------
# Process the metric values.
#------------------------------------------------------------------------------
  $msg = "generate the html file with the metrics information";
  gp_message ("verbose", $subr_name, $msg);

  $ignore_value = process_metrics ($outputdir,
				   \@sort_fields,
				   \%metric_description,
				   \%ignored_metrics);

#------------------------------------------------------------------------------
# Generate the function view html files.
#------------------------------------------------------------------------------
  $msg = "generate the function view html files";
  gp_message ("verbose", $subr_name, $msg);

  $html_first_metric_file_ref = generate_function_view (
						\$outputdir,
						\$summary_metrics,
						\$number_of_metrics,
						\@function_info,
						\%function_view_structure,
						\%function_address_info,
						\@sort_fields,
						\@exp_dir_list,
						\%addressobjtextm);

  $html_first_metric_file = ${ $html_first_metric_file_ref };

  $msg = "html_first_metric_file = " . $html_first_metric_file;
  gp_message ("debugXL", $subr_name, $msg);

  $html_test = ${ generate_home_link ("left") };
  $msg = "html_test = " . $html_test;
  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Unconditionnaly generate the page with the warnings.
#------------------------------------------------------------------------------
  $ignore_value = html_create_warnings_page (\$outputdir);

#------------------------------------------------------------------------------
# Generate the index.html file.
#------------------------------------------------------------------------------
  $msg = "generate the index.html file";
  gp_message ("verbose", $subr_name, $msg);

  $ignore_value = html_generate_index (\$outputdir,
				       \$html_first_metric_file,
				       \$summary_metrics,
				       \$number_of_metrics,
				       \@function_info,
				       \%function_address_info,
				       \@sort_fields,
				       \@exp_dir_list,
				       \%addressobjtextm,
				       \%metric_description_reversed,
				       \@table_execution_stats);

#------------------------------------------------------------------------------
# We're done.  In debug mode, print the meta data for the experiment
# directories.
#------------------------------------------------------------------------------
  $ignore_value = print_meta_data_experiments ("debug");

#------------------------------------------------------------------------------
# Before the execution completes, print the warning(s) on the screen.
#
# Note that this assumes that no additional warnings have been created since
# the call to html_create_warnings_page.  Otherwise there will be a discrepancy
# between what is printed on the screen and shown in the warnings.html page.
#------------------------------------------------------------------------------
  if (($g_total_warning_count > 0) and ($g_warnings))
    {
      $ignore_value = print_warnings_buffer ();
      @g_warning_msgs = ();
    }

#------------------------------------------------------------------------------
# This is not supposed to happen, but in case there are any fatal errors that
# have not caused the execution to terminate, print them here.
#------------------------------------------------------------------------------
  if (@g_error_msgs)
    {
      $ignore_value = print_errors_buffer (\$g_error_keyword);
    }

#------------------------------------------------------------------------------
# One line message to show where the results can be found.
#------------------------------------------------------------------------------
  my $results_file = $abs_path_outputdir . "/index.html";
  my $prologue_text = "Processing completed - view file $results_file" .
                      " in a browser";
  gp_message ("diag", $subr_name, $prologue_text);

  return (0);

} #-- End of subroutine main

#------------------------------------------------------------------------------
# If it is not present, add a "/" to the name of the argument.  This is
# intended to be used for the name of the output directory and makes it
# easier to construct pathnames.
#------------------------------------------------------------------------------
sub append_forward_slash
{
  my $subr_name = get_my_name ();

  my ($input_string) = @_;

  my $length_of_string = length ($input_string);
  my $return_string    = $input_string;

  if (rindex ($input_string, "/") != $length_of_string-1)
    {
      $return_string .= "/";
    }

  return ($return_string);

} #-- End of subroutine append_forward_slash

#------------------------------------------------------------------------------
# Return a string with a comma separated list of directory names.
#------------------------------------------------------------------------------
sub build_pretty_dir_list
{
  my $subr_name = get_my_name ();

  my ($dir_list_ref) = @_;

  my @dir_list = @{ $dir_list_ref};

  my $pretty_dir_list = join ("\n", @dir_list);

  return ($pretty_dir_list);

} #-- End of subroutine build_pretty_dir_list

#------------------------------------------------------------------------------
# Calculate the target address in hex by adding the instruction to the
# instruction address.
#------------------------------------------------------------------------------
sub calculate_target_hex_address
{
  my $subr_name = get_my_name ();

  my ($instruction_address, $instruction_offset) = @_;

  my $dec_branch_target;
  my $d1;
  my $d2;
  my $first_char;
  my $length_of_string;
  my $mask;
  my $msg;
  my $number_of_fields;
  my $raw_hex_branch_target;
  my $result;

  if ($g_addressing_mode eq "64 bit")
    {
      $mask = "0xffffffffffffffff";
      $number_of_fields = 16;
    }
  else
    {
      $msg = "g_addressing_mode = $g_addressing_mode not supported";
      gp_message ("abort", $subr_name, $msg);
    }

  $length_of_string = length ($instruction_offset);
  $first_char       = lcfirst (substr ($instruction_offset,0,1));
  $d1               = bigint::hex ($instruction_offset);
  $d2               = bigint::hex ($mask);
#          if ($first_char eq "f")
  if (($first_char =~ /[89a-f]/) and ($length_of_string == $number_of_fields))
    {
#------------------------------------------------------------------------------
# The offset is negative.  Convert to decimal and perform the subtrraction.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# XOR the decimal representation and add 1 to the result.
#------------------------------------------------------------------------------
      $result = ($d1 ^ $d2) + 1;
      $dec_branch_target = bigint::hex ($instruction_address) - $result;
    }
  else
    {
      $result = $d1;
      $dec_branch_target = bigint::hex ($instruction_address) + $result;
    }
#------------------------------------------------------------------------------
# Convert to hexadecimal.
#------------------------------------------------------------------------------
  $raw_hex_branch_target = sprintf ("%x", $dec_branch_target);

  return ($raw_hex_branch_target);

} #-- End of subroutine calculate_target_hex_address

#------------------------------------------------------------------------------
# Sets the absolute path to all commands in array @cmds.
#
# First, it is checked if the command is in the search path, built-in, or an
# alias.  If this is not the case, search for it in a couple of locations.
#
# If this all fails, warning messages are printed, but this is not a hard
# error. Yet. Most likely, things will go bad later on.
#
# The commands and their respective paths are stored in hash "g_mapped_cmds".
#------------------------------------------------------------------------------
sub check_and_define_cmds
{
  my $subr_name = get_my_name ();

  my ($cmds_ref, $search_path_ref) = @_;

#------------------------------------------------------------------------------
# Dereference the array addressess first and then store the contents.
#------------------------------------------------------------------------------
  my @cmds        = @{$cmds_ref};
  my @search_path = @{$search_path_ref};

  my @the_fields = ();

  my $cmd;
  my $cmd_found;
  my $error_code;
  my $failed_cmd;
  my $failed_cmds;
  my $found_match;
  my $mapped;
  my $msg;
  my $no_of_failed_mappings;
  my $no_of_fields;
  my $output_cmd;
  my $target_cmd;
  my $failed_mapping = $FALSE;
  my $full_path_cmd;

  gp_message ("debugXL", $subr_name, "\@cmds = @cmds");
  gp_message ("debugXL", $subr_name, "\@search_path = @search_path");

#------------------------------------------------------------------------------
# Search for the command and record the absolute path.  In case no such path
# can be found, the entry in $g_mapped_cmds is assigned a special value that
# will be checked for in the next block.
#------------------------------------------------------------------------------
  for $cmd (@cmds)
    {
      $target_cmd = "(command -v $cmd; echo \$\?)";

      $msg = "check target_cmd = " . $target_cmd;
      gp_message ("debug", $subr_name, $msg);

      ($error_code, $output_cmd) = execute_system_cmd ($target_cmd);

      if ($error_code != 0)
#------------------------------------------------------------------------------
# This is unlikely to happen, since it means the command executed failed.
#------------------------------------------------------------------------------
        {
          $msg = "error executing this command: " . $target_cmd;
          gp_message ("warning", $subr_name, $msg);
          $msg = "execution continues, but may fail later on";
          gp_message ("warning", $subr_name, $msg);

          $g_total_warning_count++;
        }
      else
#------------------------------------------------------------------------------
# So far, all is well, but is the target command available?
#------------------------------------------------------------------------------
        {
#------------------------------------------------------------------------------
# The output from the $target_cmd command should contain 2 lines in case the
# command has been found.  The first line shows the command with the full
# path, while the second line has the exit code.
#
# If the exit code is not zero, the command has not been found.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Split the output at the \n character and check the number of lines as
# well as the return code.
#------------------------------------------------------------------------------
          @the_fields   = split ("\n", $output_cmd);
          $no_of_fields = scalar (@the_fields);
          $cmd_found    = ($the_fields[$no_of_fields-1] == 0 ? $TRUE : $FALSE);

#------------------------------------------------------------------------------
# This is unexpected.  Throw an assertion error and bail out.
#------------------------------------------------------------------------------
          if ($no_of_fields > 2)
            {
              gp_message ("error", $subr_name, "output from $target_cmd:");
              gp_message ("error", $subr_name, $output_cmd);

              $msg = "the output from $target_cmd has more than 2 lines";
              gp_message ("assertion", $subr_name, $msg);
            }

          if ($cmd_found)
            {
              $full_path_cmd = $the_fields[0];
#------------------------------------------------------------------------------
# The command is in the search path.  Store the full path to the command.
#------------------------------------------------------------------------------
              $msg = "the $cmd command is in the search path";
              gp_message ("debug", $subr_name, $msg);

              $g_mapped_cmds{$cmd} = $full_path_cmd;
            }
          else
#------------------------------------------------------------------------------
# A best effort to locate the command elsewhere.  If found, store the command
# with the absolute path included.  Otherwise print a warning, but continue.
#------------------------------------------------------------------------------
            {
              $msg = "the $cmd command is not in the search path";
              $msg .= " - start a best effort search to find it";
              gp_message ("debug", $subr_name, $msg);

              $found_match = $FALSE;
              for my $path (@search_path)
                {
                  $target_cmd = $path . "/" . $cmd;
                  if (-x $target_cmd)
                    {
                      $msg = "found the command in $path";
                      gp_message ("debug", $subr_name, $msg);

                      $found_match = $TRUE;
                      $g_mapped_cmds{$cmd} = $target_cmd;
                      last;
                    }
                  else
                    {
                      $msg = "failure to find the $cmd command in $path";
                      gp_message ("debug", $subr_name, $msg);
                    }
                }

              if (not $found_match)
                {
                  $g_mapped_cmds{$cmd} = "road to nowhere";
                  $failed_mapping = $TRUE;
                }
            }
        }
    }

#------------------------------------------------------------------------------
# Scan the results stored in $g_mapped_cmds and flag errors.
#------------------------------------------------------------------------------
  $no_of_failed_mappings = 0;
  $failed_cmds           = "";

#------------------------------------------------------------------------------
# Print a warning message before showing the results, that at least one search
# has failed.
#------------------------------------------------------------------------------
  if ($failed_mapping)
    {
      $msg  = "<br>" . "failure in the verification of the OS commands:";
      gp_message ("warning", $subr_name, $msg);
    }

  while ( ($cmd, $mapped) = each %g_mapped_cmds)
    {
      if ($mapped eq "road to nowhere")
        {
          $msg  = "cannot find a path for command $cmd";
          gp_message ("warning", $subr_name, $msg);
          gp_message ("debug", $subr_name, $msg);

          $no_of_failed_mappings++;
          $failed_cmds .= $cmd;
          $g_mapped_cmds{$cmd} = $cmd;
        }
      else
       {
          $msg = "path for the $cmd command is $mapped";
          gp_message ("debug", $subr_name, $msg);
       }
    }
  if ($no_of_failed_mappings != 0)
    {
      my $plural_1 = ($no_of_failed_mappings > 1) ? "failures"   : "failure";
      my $plural_2 = ($no_of_failed_mappings > 1) ? "commands" : "command";

      $msg  = "encountered $no_of_failed_mappings $plural_1 to locate";
      $msg .= " selected " . $plural_2;
      gp_message ("warning", $subr_name, $msg);
      gp_message ("debug", $subr_name, $msg);

      $msg  = "execution continues, but may fail later on";
      gp_message ("warning", $subr_name, $msg);
      gp_message ("debug", $subr_name, $msg);

      $g_total_warning_count++;
    }

  return ($no_of_failed_mappings);

} #-- End of subroutine check_and_define_cmds

#------------------------------------------------------------------------------
# Look for a branch instruction, or the special endbr32/endbr64 instruction
# that is also considered to be a branch target.  Note that the latter is x86
# specific.
#------------------------------------------------------------------------------
sub check_and_proc_dis_branches
{
  my $subr_name = get_my_name ();

  my ($input_line_ref, $line_no_ref,  $branch_target_ref,
      $extended_branch_target_ref, $branch_target_no_ref_ref) = @_;

  my $input_line = ${ $input_line_ref };
  my $line_no    = ${ $line_no_ref };
  my %branch_target = %{ $branch_target_ref };
  my %extended_branch_target = %{ $extended_branch_target_ref };
  my %branch_target_no_ref = %{ $branch_target_no_ref_ref };

  my $found_it = $TRUE;
  my $hex_branch_target;
  my $instruction_address;
  my $instruction_offset;
  my $msg;
  my $raw_hex_branch_target;

  if (   ($input_line =~ /$g_branch_regex/)
      or ($input_line =~ /$g_endbr_regex/))
    {
      if (defined ($3))
        {
          $msg = "found a branch or endbr instruction: " .
                 "\$1 = $1 \$2 = $2 \$3 = $3";
        }
      else
        {
          $msg = "found a branch or endbr instruction: " .
                 "\$1 = $1 \$2 = $2";
        }
      gp_message ("debugXL", $subr_name, $msg);

      if (defined ($1))
        {
#------------------------------------------------------------------------------
# Found a qualifying instruction
#------------------------------------------------------------------------------
          $instruction_address = $1;
          if (defined ($3))
            {
#------------------------------------------------------------------------------
# This must be the branch target and needs to be converted and processed.
#------------------------------------------------------------------------------
              $instruction_offset  = $3;
              $raw_hex_branch_target = calculate_target_hex_address (
                                        $instruction_address,
                                        $instruction_offset);

              $hex_branch_target = "0x" . $raw_hex_branch_target;
              $branch_target{$hex_branch_target} = 1;
              $extended_branch_target{$instruction_address} =
							$raw_hex_branch_target;
            }
          if (defined ($2) and (not defined ($3)))
            {
#------------------------------------------------------------------------------
# Unlike a branch, the endbr32/endbr64 instructions do not have a second field.
#------------------------------------------------------------------------------
              my $instruction_name = $2;
              if ($instruction_name =~ /$g_endbr_inst_regex/)
                {
                  my $msg = "found endbr: $instruction_name " .
                            $instruction_address;
                  gp_message ("debugXL", $subr_name, $msg);
                  $raw_hex_branch_target = $instruction_address;

                  $hex_branch_target = "0x" . $raw_hex_branch_target;
                  $branch_target_no_ref{$instruction_address} = 1;
                }
            }
        }
      else
        {
#------------------------------------------------------------------------------
# TBD: Perhaps this should be an assertion or alike.
#------------------------------------------------------------------------------
          $branch_target{"0x0000"} = $FALSE;
          $msg = "cannot determine branch target";
          gp_message ("debug", $subr_name, $msg);
        }
    }
  else
    {
      $found_it = $FALSE;
    }

  return (\$found_it, \%branch_target, \%extended_branch_target,
         \%branch_target_no_ref);

} #-- End of subroutine check_and_proc_dis_branches

#------------------------------------------------------------------------------
# Check an input line from the disassembly file to include a function call.
# If it does, process the line and return the branch target results.
#------------------------------------------------------------------------------
sub check_and_proc_dis_func_call
{
  my $subr_name = get_my_name ();

  my ($input_line_ref, $line_no_ref,  $branch_target_ref,
      $extended_branch_target_ref) = @_;

  my $input_line = ${ $input_line_ref };
  my $line_no    = ${ $line_no_ref };
  my %branch_target = %{ $branch_target_ref };
  my %extended_branch_target = %{ $extended_branch_target_ref };

  my $found_it = $TRUE;
  my $hex_branch_target;
  my $instruction_address;
  my $instruction_offset;
  my $msg;
  my $raw_hex_branch_target;

  if ( $input_line =~ /$g_function_call_v2_regex/ )
    {
      $msg = "found a function call - line[$line_no] = $input_line";
      gp_message ("debugXL", $subr_name, $msg);
      if (not defined ($2))
        {
          $msg = "line[$line_no] " .
                 "an instruction address is expected, but not found";
          gp_message ("assertion", $subr_name, $msg);
        }
      else
        {
          $instruction_address = $2;

          $msg = "instruction_address = $instruction_address";
          gp_message ("debugXL", $subr_name, $msg);

          if (not defined ($4))
            {
              $msg = "line[$line_no] " .
                     "an address offset is expected, but not found";
              gp_message ("assertion", $subr_name, $msg);
            }
          else
            {
              $instruction_offset = $4;
              if ($instruction_offset =~ /[0-9a-fA-F]+/)
                {
                  $msg = "calculate branch target: " .
                         "instruction_address = $instruction_address";
                  gp_message ("debugXL", $subr_name, $msg);
                  $msg = "calculate branch target: " .
                         "instruction_offset  = $instruction_offset";
                  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# The instruction offset needs to be converted and added to the instruction
# address.
#------------------------------------------------------------------------------
                  $raw_hex_branch_target = calculate_target_hex_address (
                                            $instruction_address,
                                            $instruction_offset);
                  $hex_branch_target     = "0x" . $raw_hex_branch_target;

                  $msg = "calculated hex_branch_target = " .
                         $hex_branch_target;
                  gp_message ("debugXL", $subr_name, $msg);

                  $branch_target{$hex_branch_target} = 1;
                  $extended_branch_target{$instruction_address} =
							$raw_hex_branch_target;

                  $msg = "set branch_target{$hex_branch_target} to 1";
                  gp_message ("debugXL", $subr_name, $msg);
                  $msg  = "added extended_branch_target{$instruction_address}";
                  $msg .= " = $extended_branch_target{$instruction_address}";
                  gp_message ("debugXL", $subr_name, $msg);
                }
              else
                {
                  $msg = "line[$line_no] unknown address format";
                  gp_message ("assertion", $subr_name, $msg);
                }
            }
        }
    }
  else
    {
      $found_it = $FALSE;
    }

  return (\$found_it, \%branch_target, \%extended_branch_target);

} #-- End of subroutine check_and_proc_dis_func_call

#------------------------------------------------------------------------------
# Check if the value for the user option given is valid.
#
# In case the value is valid, the g_user_settings table is updated with the
# (new) value.
#
# Otherwise an error message is pushed into the g_error_msgs buffer.
#
# The return value is TRUE/FALSE.
#------------------------------------------------------------------------------
sub check_and_set_user_option
{
  my $subr_name = get_my_name ();

  my ($internal_opt_name, $value) = @_;

  my $msg;
  my $valid;
  my $option_value_missing;

  my $option     = $g_user_settings{$internal_opt_name}{"option"};
  my $data_type  = $g_user_settings{$internal_opt_name}{"data_type"};
  my $no_of_args = $g_user_settings{$internal_opt_name}{"no_of_arguments"};
 
  if (($no_of_args >= 1) and
      ((not defined ($value)) or (length ($value) == 0)))
#------------------------------------------------------------------------------
# If there was no value given, but it is required, flag an error.
# There could also be a value, but it might be the empty string.
#
# Note that that there are currently no options with multiple values.  Should
# these be introduced, the current check may need to be refined.
#------------------------------------------------------------------------------
    {
      $valid                = $FALSE;
      $option_value_missing = $TRUE;
    }
  elsif ($no_of_args >= 1)
    {
      $option_value_missing = $FALSE;
#------------------------------------------------------------------------------
# There is an input value.  Check if it is valid and if so, store it.
#
# Note that we allow the options to be case insensitive.
#------------------------------------------------------------------------------
      $valid = verify_if_input_is_valid ($value, $data_type);

      if ($valid)
        {
          if (($data_type eq "onoff") or ($data_type eq "size"))
            {
              $g_user_settings{$internal_opt_name}{"current_value"} =
								lc ($value);
            }
          else
            {
              $g_user_settings{$internal_opt_name}{"current_value"} = $value;
            }
          $g_user_settings{$internal_opt_name}{"defined"} = $TRUE;
        }
    }

  return (\$valid, \$option_value_missing);

} #-- End of subroutine check_and_set_user_option

#------------------------------------------------------------------------------
# Check for the $GP_DISPLAY_TEXT tool to be available.  This is a critical tool
# needed to provide the information.  If it can not be found, execution is
# terminated.
#
# We first search for this tool in the current execution directory.  If it
# cannot be found there, use $PATH to try to locate it.
#------------------------------------------------------------------------------
sub check_availability_tool
{
  my $subr_name = get_my_name ();

  my ($location_gp_command_ref) = @_;

  my $error_code;
  my $error_occurred;
  my $gp_path;
  my $msg;
  my $output_which_gp_display_text;
  my $return_value;
  my $target_cmd;

#------------------------------------------------------------------------------
# Get the path to gprofng-display-text.
#------------------------------------------------------------------------------
  my ($error_occurred_ref, $gp_path_ref, $return_value_ref) =
		       find_path_to_gp_display_text ($location_gp_command_ref);

  $error_occurred = ${ $error_occurred_ref};
  $gp_path        = ${ $gp_path_ref };
  $return_value   = ${ $return_value_ref};

  $msg = "error_occurred = $error_occurred return_value = $return_value";
  gp_message ("debugXL", $subr_name, $msg);

  if (not $error_occurred)
#------------------------------------------------------------------------------
# All is well and gprofng-display-text has been located.
#------------------------------------------------------------------------------
    {
      $g_path_to_tools = $return_value;

      $msg = "located $GP_DISPLAY_TEXT in the execution directory";
      gp_message ("debug", $subr_name, $msg);
      $msg = "g_path_to_tools = $g_path_to_tools";
      gp_message ("debug", $subr_name, $msg);
    }
  else
#------------------------------------------------------------------------------
# Something went wrong, but perhaps we can still continue.  Try to find
# $GP_DISPLAY_TEXT through the search path.
#------------------------------------------------------------------------------
    {
      $msg  = $g_html_new_line;
      $msg .= "could not find $GP_DISPLAY_TEXT in directory $gp_path :";
      $msg .= " $return_value";
      gp_message ("warning", $subr_name, $msg);

#------------------------------------------------------------------------------
# Check if we can find $GP_DISPLAY_TEXT in the search path.
#------------------------------------------------------------------------------
      $msg = "check for $GP_DISPLAY_TEXT to be in the search path";
      gp_message ("debug", $subr_name, $msg);

      gp_message ("warning", $subr_name, $msg);
      $g_total_warning_count++;

      $target_cmd = $g_mapped_cmds{"which"} . " $GP_DISPLAY_TEXT 2>&1";

      ($error_code, $output_which_gp_display_text) =
					      execute_system_cmd ($target_cmd);

      if ($error_code == 0)
        {
          my ($gp_file_name, $gp_path, $suffix_not_used) =
                                     fileparse ($output_which_gp_display_text);
          $g_path_to_tools = $gp_path;

          $msg  = "located $GP_DISPLAY_TEXT in $g_path_to_tools";
          gp_message ("warning", $subr_name, $msg);
          $msg = "this is the version that will be used";
          gp_message ("warning", $subr_name, $msg);

          $msg = "the $GP_DISPLAY_TEXT tool is in the search path";
          gp_message ("debug", $subr_name, $msg);
          $msg = "g_path_to_tools = $g_path_to_tools";
          gp_message ("debug", $subr_name, $msg);
        }
      else
        {
          $msg = "failure to find $GP_DISPLAY_TEXT in the search path";
          gp_message ("error", $subr_name, $msg);

          $g_total_error_count++;

          gp_message ("abort", $subr_name, $g_abort_msg);
        }
     }

  return (\$g_path_to_tools);

} #-- End of subroutine check_availability_tool

#------------------------------------------------------------------------------
# This function determines whether load objects are in ELF format.
#
# Compared to the original code, any input value other than 2 or 3 is rejected
# upfront.  This not only reduces the nesting level, but also eliminates a
# possible bug.
#
# Also, by isolating the tests for the input files, another nesting level could
# be eliminated, further simplifying this still too complex code.
#------------------------------------------------------------------------------
sub check_loadobjects_are_elf
{
  my $subr_name = get_my_name ();

  my ($selected_archive) = @_;

  my $event_kind_map_regex;
  $event_kind_map_regex  = '^<event kind="map"\s.*vaddr=';
  $event_kind_map_regex .= '"0x([0-9a-fA-F]+)"\s+.*foffset=';
  $event_kind_map_regex .= '"\+*0x([0-9a-fA-F]+)"\s.*modes=';
  $event_kind_map_regex .= '"0x([0-9]+)"\s.*name="(.*)".*>$';

  my $hostname_current = $local_system_config{"hostname_current"};
  my $arch             = $local_system_config{"processor"};
  my $arch_uname_s     = $local_system_config{"kernel_name"};

  my $extracted_information;

  my $elf_magic_number;

  my $executable_name;
  my $va_executable_in_hex;

  my $arch_exp;
  my $hostname_exp;
  my $os_exp;
  my $os_exp_full;

  my $archives_file;
  my $rc_b;
  my $file;
  my $line;
  my $msg;
  my $name;
  my $name_path;
  my $foffset;
  my $vaddr;
  my $modes;

  my $path_to_map_file;
  my $path_to_log_file;

#------------------------------------------------------------------------------
# TBD: Parameterize and should be the first experiment directory from the list.
#------------------------------------------------------------------------------
  $path_to_log_file  =
		$g_exp_dir_meta_data{$selected_archive}{"directory_path"};
  $path_to_log_file .= $selected_archive;
  $path_to_log_file .= "/log.xml";

  gp_message ("debug", $subr_name, "hostname_current = $hostname_current");
  gp_message ("debug", $subr_name, "arch             = $arch");
  gp_message ("debug", $subr_name, "arch_uname_s     = $arch_uname_s");

#------------------------------------------------------------------------------
# TBD
#
# This check can probably be removed since the presence of the log.xml file is
# checked for in an earlier phase.
#------------------------------------------------------------------------------
  $msg  = " - unable to open file $path_to_log_file for reading:";
  open (LOG_XML, "<", $path_to_log_file)
    or die ($subr_name . $msg . " " . $!);

  $msg = "opened file $path_to_log_file for reading";
  gp_message ("debug", $subr_name, $msg);

  while (<LOG_XML>)
    {
      $line = $_;
      chomp ($line);
      gp_message ("debugM", $subr_name, "read line: $line");
#------------------------------------------------------------------------------
# Search for the first line starting with "<system".  Bail out if found and
# parsed. These are two examples:
# <system hostname="ruud-vm" arch="x86_64" \
# os="Linux 4.14.35-2025.400.8.el7uek.x86_64" pagesz="4096" npages="30871514">
#------------------------------------------------------------------------------
      if ($line =~ /^\s*<system\s+/)
        {
          $msg = "selected the following line from the log.xml file:";
          gp_message ("debugM", $subr_name, $msg);
          gp_message ("debugM", $subr_name, "$line");
          if ($line =~ /.*\s+hostname="([^"]+)/)
            {
              $hostname_exp = $1;
              $msg = "extracted hostname_exp = " . $hostname_exp;
              gp_message ("debugM", $subr_name, $msg);
            }
          if ($line =~ /.*\s+arch="([^"]+)/)
            {
              $arch_exp = $1;
              $msg = "extracted arch_exp = " . $arch_exp;
              gp_message ("debugM", $subr_name, $msg);
            }
          if ($line =~ /.*\s+os="([^"]+)/)
            {
              $os_exp_full = $1;
#------------------------------------------------------------------------------
# Capture the first word only.
#------------------------------------------------------------------------------
              if ($os_exp_full =~ /([^\s]+)/)
                {
                  $os_exp = $1;
                }
              $msg = "extracted os_exp = " . $os_exp;
              gp_message ("debugM", $subr_name, $msg);
            }
          last;
        }
    } #-- End of while loop

  close (LOG_XML);

#------------------------------------------------------------------------------
# If the current system is identical to the system used in the experiment,
# we can return early.  Otherwise we need to dig deeper.
#
# TBD: How about the other experiment directories?! This needs to be fixed.
#------------------------------------------------------------------------------

  gp_message ("debug", $subr_name, "completed while loop");
  gp_message ("debug", $subr_name, "hostname_exp     = $hostname_exp");
  gp_message ("debug", $subr_name, "arch_exp         = $arch_exp");
  gp_message ("debug", $subr_name, "os_exp           = $os_exp");

#TBD: THIS DOES NOT CHECK IF ELF IS FOUND!

  if (($hostname_current eq $hostname_exp) and
      ($arch             eq $arch_exp)     and
      ($arch_uname_s     eq $os_exp))
        {
          $msg  = "early return: the hostname, architecture and OS match";
          $msg .= " the current system";
          gp_message ("debug", $subr_name, $msg);
          $msg = "FAKE THIS IS NOT THE CASE AND CONTINUE";
          gp_message ("debug", $subr_name, $msg);
# FAKE          return ($TRUE);
        }

  if (not $g_exp_dir_meta_data{$selected_archive}{"archive_is_empty"})
    {
      $msg = "selected_archive = " . $selected_archive;
      gp_message ("debug", $subr_name, $msg);
      for my $i (sort keys
		   %{$g_exp_dir_meta_data{$selected_archive}{"archive_files"}})
        {
          $msg  = "stored loadobject " . $i . " ";
          $msg .= $g_exp_dir_meta_data{$selected_archive}{"archive_files"}{$i};
          gp_message ("debug", $subr_name, $msg);
        }
    }

#------------------------------------------------------------------------------
# Check if the selected experiment directory has archived files in ELF format.
# If not, use the information in map.xml to get the name of the executable
# and the virtual address.
#------------------------------------------------------------------------------

  if ($g_exp_dir_meta_data{$selected_archive}{"archive_in_elf_format"})
    {
      $msg  = "the files in directory $selected_archive/archives are in";
      $msg .= " ELF format";
      gp_message ("debugM", $subr_name, $msg);
      $msg = "IGNORE THIS AND USE MAP.XML";
      gp_message ("debugM", $subr_name, $msg);
##      return ($TRUE);
    }

  $msg  = "the files in directory $selected_archive/archives are not in";
  $msg .= " ELF format";
  gp_message ("debug", $subr_name, $msg);

  $path_to_map_file  =
		$g_exp_dir_meta_data{$selected_archive}{"directory_path"};
  $path_to_map_file .= $selected_archive;
  $path_to_map_file .= "/map.xml";

  $msg  = " - unable to open file $path_to_map_file for reading:";
  open (MAP_XML, "<", $path_to_map_file)
    or die ($subr_name . $msg . " " . $!);
  $msg = "opened file $path_to_map_file for reading";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Scan the map.xml file.  We need to find the name of the executable with the
# mode set to 0x005.  For this entry we have to capture the virtual address.
#------------------------------------------------------------------------------
  $extracted_information = $FALSE;
  while (<MAP_XML>)
    {
      $line = $_;
      chomp ($line);
      gp_message ("debugM", $subr_name, "MAP_XML read line = $line");
#------------------------------------------------------------------------------
# Replaces this way too long line:
#     if ($line =~   /^<event kind="map"\s.*vaddr="0x([0-9a-fA-F]+)"\s+.
#     *foffset="\+*0x([0-9a-fA-F]+)"\s.*modes="0x([0-9]+)"\s.*
#     name="(.*)".*>$/)
#------------------------------------------------------------------------------
      if ($line =~ /$event_kind_map_regex/)
        {
          gp_message ("debugM", $subr_name, "target line = $line");
          $vaddr     = $1;
          $foffset   = $2;
          $modes     = $3;
          $name_path = $4;
          $name      = get_basename ($name_path);
          $msg  = "extracted vaddr     = $vaddr foffset = $foffset";
          $msg .= " modes = $modes";
          gp_message ("debugM", $subr_name, $msg);
          $msg = "extracted name_path = $name_path name = $name";
          gp_message ("debugM", $subr_name, $msg);
#              $error_extracting_information = $TRUE;
          $executable_name  = $name;
          my $result_VA = bigint::hex ($vaddr) - bigint::hex ($foffset);
          my $hex_VA = sprintf ("0x%016x", $result_VA);
          $va_executable_in_hex = $hex_VA;

          $msg = "set executable_name      = " . $executable_name;
          gp_message ("debugM", $subr_name, $msg);
          $msg = "set va_executable_in_hex = " . $va_executable_in_hex;
          gp_message ("debugM", $subr_name, $msg);
          $msg = "result_VA                = " . $result_VA;
          gp_message ("debugM", $subr_name, $msg);
          $msg = "hex_VA                   = " . $hex_VA;
          gp_message ("debugM", $subr_name, $msg);

          if ($modes eq "005")
            {
              $extracted_information = $TRUE;
              last;
            }
        }
    }

  close (MAP_XML);

  if (not $extracted_information)
    {
      $msg  = "cannot find the necessary information in";
      $msg .= " the $path_to_map_file file";
      gp_message ("assertion", $subr_name, $msg);
    }

##  $executable_name = $ARCHIVES_MAP_NAME;
##  $va_executable_in_hex = $ARCHIVES_MAP_VADDR;

  return ($executable_name, $va_executable_in_hex);

} #-- End of subroutine check_loadobjects_are_elf

#------------------------------------------------------------------------------
# Compare the current metric values against the maximum values.  Mark the line
# if a value is within the percentage defined by $hp_value.
#------------------------------------------------------------------------------
sub check_metric_values
{
  my $subr_name = get_my_name ();

  my ($metric_values, $max_metric_values_ref) = @_;

  my @max_metric_values = @{ $max_metric_values_ref };

  my @current_metrics = ();
  my $colour_coded_line;
  my $current_value;
  my $hp_value = $g_user_settings{"highlight_percentage"}{"current_value"};
  my $max_value;
  my $msg;
  my $relative_distance;

  @current_metrics   = split (" ", $metric_values);
  $colour_coded_line = $FALSE;

  for my $metric (0 .. $#current_metrics)
    {
      $current_value = $current_metrics[$metric];
      if (exists ($max_metric_values[$metric]))
        {
          $max_value     = $max_metric_values[$metric];

          $msg  = "metric = $metric current_value = $current_value";
          $msg .= " max_value = $max_value";
          gp_message ("debugXL", $subr_name, $msg);

          if ( ($max_value > 0) and ($current_value > 0) and
	       ($current_value != $max_value) )
            {
# TBD: abs needed?
              $msg  = "metric = $metric current_value = $current_value";
              $msg .= " max_value = $max_value";
              gp_message ("debugXL", $subr_name, $msg);

              $relative_distance = 1.00 - abs (
				($max_value - $current_value)/$max_value );

              $msg = "relative_distance = $relative_distance";
              gp_message ("debugXL", $subr_name, $msg);

              if ($relative_distance >= $hp_value/100.0)
                {
                  $msg = "metric $metric is within the relative_distance";
                  gp_message ("debugXL", $subr_name, $msg);

                  $colour_coded_line = $TRUE;
                  last;
                }
            }
        }
    } #-- End of loop over metrics

  return (\$colour_coded_line);

} #-- End of subroutine check_metric_values

#------------------------------------------------------------------------------
# Check if the system is supported.
#------------------------------------------------------------------------------
sub check_support_for_processor
{
  my $subr_name = get_my_name ();

  my ($machine_ref) = @_;

  my $machine = ${ $machine_ref };
  my $is_supported;

  if ($machine eq "x86_64")
    {
      $is_supported = $TRUE;
    }
  else
    {
      $is_supported = $FALSE;
    }

  return (\$is_supported);

} #-- End of subroutine check_support_for_processor

#------------------------------------------------------------------------------
# Check the command line options for the occurrence of experiments and make
# sure that this list is contigious.  No other names are allowed in this list.
#
# Terminate execution in case of an error.  Otherwise remove the experiment
# names for ARGV (to make the subsequent parsing easier), and return an array
# with the experiment names.
#
# The following patterns are supposed to be detected:
#
# <expdir_1> some other word(s) <expdir_2>
# <expdir> some other word(s)
#------------------------------------------------------------------------------
sub check_the_experiment_list
{
  my $subr_name = get_my_name ();

#------------------------------------------------------------------------------
# The name of an experiment directory can contain any non-whitespace
# character(s), but has to end with .er, or optionally .er/.  Multiple
# forward slashes are allowed.
#------------------------------------------------------------------------------
  my $exp_dir_regex = '^(\S+)(\.er)\/*$';
  my $forward_slash_regex = '\/*$';

  my $current_value;
  my @exp_dir_list = ();
  my $found_experiment = $FALSE;
  my $found_non_exp = $FALSE;
  my $msg;
  my $name_non_exp_dir = "";
  my $no_of_experiments = 0;
  my $no_of_invalid_dirs = 0;
  my $opt_remainder;
  my $valid = $TRUE;

  for my $i (keys @ARGV)
    {
      $current_value = $ARGV[$i];
      if ($current_value =~ /$exp_dir_regex/)
#------------------------------------------------------------------------------
# The current value is an experiment.  Remove any trailing forward slashes,
# Increment the count, push the value into the array and set the
# found_experiment flag to TRUE.
#------------------------------------------------------------------------------
        {
          $no_of_experiments += 1;

          $current_value =~ s/$forward_slash_regex//;
          push (@exp_dir_list, $current_value);

          if (not $found_experiment)
#------------------------------------------------------------------------------
# Start checking for the next field(s).
#------------------------------------------------------------------------------
            {
              $found_experiment = $TRUE;
            }
#------------------------------------------------------------------------------
# We had found non-experiment names and now see another experiment.  Time to
# bail out of the loop.
#------------------------------------------------------------------------------
          if ($found_non_exp)
            {
              last;
            }
        }
      else
        {
          if ($found_experiment)
#------------------------------------------------------------------------------
# The current value is not an experiment, but the value of found_experiment
# indicates at least one experiment has been seen already.  This means that
# the list of experiment names is not contiguous and that is a fatal error.
#------------------------------------------------------------------------------
            {
              $name_non_exp_dir .= $current_value . " ";
              $found_non_exp = $TRUE;
            }
        }

    }

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Error handling.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

  if ($found_non_exp)
#------------------------------------------------------------------------------
# The experiment list is not contiguous.
#------------------------------------------------------------------------------
    {
      $valid = $FALSE;
      $msg = "the list with the experiments is not contiguous:";
      gp_message ("error", $subr_name, $msg);

      $msg = "\"" . $name_non_exp_dir. "\"". " is not an experiment, but" .
             " appears in a list where experiments are expected";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }

  if ($no_of_experiments == 0)
#------------------------------------------------------------------------------
# The experiment list is empty.
#------------------------------------------------------------------------------
    {
      $valid = $FALSE;
      $msg = "the experiment list is missing from the options";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }

  if (not $valid)
#------------------------------------------------------------------------------
# If an error has occurred, print the error(s) and terminate execution.
#------------------------------------------------------------------------------
    {
      gp_message ("abort", $subr_name, $g_abort_msg);
    }

#------------------------------------------------------------------------------
# We now have a list with experiments, but we still need to verify whether they
# exist, and if so, are these valid experiments?
#------------------------------------------------------------------------------
  for my $exp_dir (@exp_dir_list)
    {
      $msg = "checking experiment directory $exp_dir";
      gp_message ("debug", $subr_name, $msg);

      if (-d $exp_dir)
        {
          $msg = "directory $exp_dir found";
          gp_message ("debug", $subr_name, $msg);
#------------------------------------------------------------------------------
# Files log.xml and map.xml have to be there.
#------------------------------------------------------------------------------
          if ((-e $exp_dir."/log.xml") and (-e $exp_dir."/map.xml"))
            {
              $msg  = "directory $exp_dir appears to be a valid experiment";
              $msg .= " directory";
              gp_message ("debug", $subr_name, $msg);
            }
          else
            {
              $no_of_invalid_dirs++;
              $msg  = "file " . $exp_dir . "/log.xml and/or " . $exp_dir;
              $msg .= "/map.xml missing";
              gp_message ("debug", $subr_name, $msg);

              $msg  = "directory " . get_basename($exp_dir) . " does not";
              $msg .= " appear to be a valid experiment directory";
              gp_message ("error", $subr_name, $msg);

              $g_total_error_count++;
            }
        }
      else
        {
          $no_of_invalid_dirs++;
          $msg  = "directory " . get_basename($exp_dir) . " does not exist";
          gp_message ("error", $subr_name, $msg);

          $g_total_error_count++;
        }
    }

  if ($no_of_invalid_dirs > 0)
#------------------------------------------------------------------------------
# This is a fatal error, but for now, we can continue to check for more errors.
# Even if none more are found, execution is terminated before the data is
# generated and processed.  In this way we can catch as many errors as
# possible.
#------------------------------------------------------------------------------
    {
      my $plural_or_single = ($no_of_invalid_dirs == 1) ?
		"one experiment is" : $no_of_invalid_dirs . " experiments are";

      $msg = $plural_or_single . " not valid";
##      gp_message ("abort", $subr_name, $msg);

##      $g_total_error_count++;
    }

#------------------------------------------------------------------------------
# Remove the experiments from ARGV and return the array with the experiment
# names.  Note that these may, or may not be valid, but if invalid, execution
# terminates before they are used.
#------------------------------------------------------------------------------
  for my $i (1 .. $no_of_experiments)
    {
      my $poppy = pop (@ARGV);

      $msg = "popped $poppy from ARGV";
      gp_message ("debug", $subr_name, $msg);

      $msg = "ARGV after update = " . join (" ", @ARGV);
      gp_message ("debug", $subr_name, $msg);
    }

  return (\@exp_dir_list);

} #-- End of subroutine check_the_experiment_list

#------------------------------------------------------------------------------
# Perform multiple checks on the experiment directories.
#
# TBD: It needs to be investigated whether all of this is really neccesary.
#------------------------------------------------------------------------------
sub check_validity_exp_dirs
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref) = @_;

  my @exp_dir_list = @{ $exp_dir_list_ref };

  my %elf_rats = ();

  my $dir_not_found    = $FALSE;
  my $missing_dirs     = 0;
  my $invalid_dirs     = 0;

  my $archive_dir_not_empty;
  my $archives_dir;
  my $archives_file;
  my $count_exp_dir_not_elf;
  my $elf_magic_number;
  my $first_line;
  my $msg;

  my $first_time;
  my $filename;

  my $comment;

  my $selected_archive_has_elf_format;

  my $selected_archive;
  my $archive_dir_selected;
  my $no_of_files_in_selected_archive;

#------------------------------------------------------------------------------
# Initialize ELF status to FALSE.
#------------------------------------------------------------------------------
##  for my $exp_dir (@exp_dir_list)
  for my $exp_dir (keys %g_exp_dir_meta_data)
    {
      $g_exp_dir_meta_data{$exp_dir}{"elf_format"} = $FALSE;
      $g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"} = $FALSE;
    }
#------------------------------------------------------------------------------
# Check if the load objects are in ELF format.
#------------------------------------------------------------------------------
  for my $exp_dir (keys %g_exp_dir_meta_data)
    {
      $archives_dir  = $g_exp_dir_meta_data{$exp_dir}{"directory_path"};
      $archives_dir .= $exp_dir . "/archives";
      $archive_dir_not_empty = $FALSE;
      $first_time            = $TRUE;
      $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"} = $TRUE;
      $g_exp_dir_meta_data{$exp_dir}{"no_of_files_in_archive"} = 0;

      $msg = "g_exp_dir_meta_data{$exp_dir}{'archive_is_empty'} = ";
      $msg .= $g_exp_dir_meta_data{$exp_dir}{'archive_is_empty'};
      gp_message ("debug", $subr_name, $msg);

      $msg = "checking $archives_dir";
      gp_message ("debug", $subr_name, $msg);

      while (glob ("$archives_dir/*"))
        {
          $filename = get_basename ($_);

          $msg = "processing file: $filename";
          gp_message ("debug", $subr_name, $msg);

          $g_exp_dir_meta_data{$exp_dir}{"archive_files"}{$filename} = $TRUE;
          $g_exp_dir_meta_data{$exp_dir}{"no_of_files_in_archive"}++;

          $archive_dir_not_empty = $TRUE;
#------------------------------------------------------------------------------
# Replaces the ELF_RATS part in elf_phdr.
#
# Challenge:  splittable_mrg.c_I0txnOW_Wn5
#
# TBD: Store this for each relevant experiment directory.
#------------------------------------------------------------------------------
          my $last_dot              = rindex ($filename,".");
          my $underscore_before_dot = $TRUE;
          my $first_underscore      = -1;

          $msg = "last_dot = $last_dot";
          gp_message ("debugXL", $subr_name, $msg);

          while ($underscore_before_dot)
            {
              $first_underscore = index ($filename, "_", $first_underscore+1);
              if ($last_dot < $first_underscore)
                {
                  $underscore_before_dot = $FALSE;
                }
            }
          my $original_name  = substr ($filename, 0, $first_underscore);
          $msg = "stripped archive name: " . $original_name;
          gp_message ("debug", $subr_name, $msg);
          if (not exists ($elf_rats{$original_name}))
            {
              $elf_rats{$original_name} = [$filename, $exp_dir];
            }
#------------------------------------------------------------------------------
# We only need to detect the presence of an object once.
#------------------------------------------------------------------------------
          if ($first_time)
            {
              $first_time = $FALSE;
              $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"} = $FALSE;
              $msg  = "g_exp_dir_meta_data{$exp_dir}{'archive_is_empty'} = ";
              $msg .= $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"};

              gp_message ("debugXL", $subr_name, $msg);
            }
        }
    } #-- End of loop over experiment directories

  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      my $empty = $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"};
      $msg  = "archive directory " . $exp_dir . "/archives is";
      $msg .= " " . ($empty ? "empty" : "not empty");
      gp_message ("debug", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Verify that all relevant files in the archive directories are in ELF format.
#------------------------------------------------------------------------------
  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      $g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"} = $FALSE;
      if (not $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"})
        {
          $archives_dir  = $g_exp_dir_meta_data{$exp_dir}{"directory_path"};
          $archives_dir .= $exp_dir . "/archives";
          $msg = "exp_dir = " . $exp_dir . " archives_dir = " . $archives_dir;
          gp_message ("debug", $subr_name, $msg);
#------------------------------------------------------------------------------
# Check if any of the loadobjects is of type ELF.  Bail out on the first one
# found.  The assumption is that all other loadobjects must be of type ELF too
# then.
#------------------------------------------------------------------------------
          for my $aname (sort keys
			%{$g_exp_dir_meta_data{$exp_dir}{"archive_files"}})
            {
              $filename  = $g_exp_dir_meta_data{$exp_dir}{"directory_path"};
              $filename .=  $exp_dir . "/archives/" . $aname;
              $msg  = " - unable to open file $filename for reading:";
              open (ARCF,"<", $filename)
                or die ($subr_name . $msg . " " . $!);

              $first_line = <ARCF>;
              close (ARCF);

#------------------------------------------------------------------------------
# The first 4 hex fields in the header of an ELF file are: 7F 45 4c 46 (7FELF).
#
# See also https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
#------------------------------------------------------------------------------
#              if ($first_line =~ /^\177ELF.*/)

              $elf_magic_number = unpack ('H8', $first_line);
              if ($elf_magic_number eq "7f454c46")
                {
                  $g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"} =
									$TRUE;
                  $g_exp_dir_meta_data{$exp_dir}{"elf_format"} = $TRUE;
                  last;
                }
            }
        }
    }

  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      $msg = "the loadobjects in the archive in $exp_dir are";
      $msg .= ($g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"}) ?
							" in" : " not in";
      $msg .= " ELF format";
      gp_message ("debug", $subr_name, $msg);
    }
  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      if ($g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"})
        {
          $msg = "there are no archived files in " . $exp_dir;
          gp_message ("debug", $subr_name, $msg);
        }
    }

#------------------------------------------------------------------------------
# If there are archived files and they are not in ELF format, a debug message
# is issued.
#
# TBD: Bail out?
#------------------------------------------------------------------------------
  $count_exp_dir_not_elf = 0;
  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      if (not $g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"})
        {
          $count_exp_dir_not_elf++;
        }
    }
  if ($count_exp_dir_not_elf != 0)
    {
      $msg  = "there are $count_exp_dir_not_elf experiments with non-ELF";
      $msg .= " load objects";
      gp_message ("debug", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Select the experiment directory that is used for the files in the archive.
# By default, a directory with archived files is used, but in case this does
# not exist, a directory without archived files is selected.  Obviously this
# needs to be dealt with later on.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Try the experiments with archived files first.
#------------------------------------------------------------------------------
  $archive_dir_not_empty = $FALSE;
  $archive_dir_selected  = $FALSE;
##  for my $exp_dir (sort @exp_dir_list)
  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      $msg = "exp_dir = " . $exp_dir;
      gp_message ("debugXL", $subr_name, $msg);
      $msg  = "g_exp_dir_meta_data{$exp_dir}{'archive_is_empty'}";
      $msg .= " = " . $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"};
      gp_message ("debugXL", $subr_name, $msg);

      if (not $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"})
        {
          $selected_archive      = $exp_dir;
          $archive_dir_not_empty = $TRUE;
          $archive_dir_selected  = $TRUE;
          $selected_archive_has_elf_format =
		($g_exp_dir_meta_data{$exp_dir}{"archive_in_elf_format"}) ?
								$TRUE : $FALSE;
          last;
        }
    }
  if (not $archive_dir_selected)
#------------------------------------------------------------------------------
# None are found and pick the first one without archived files.
#------------------------------------------------------------------------------
    {
      for my $exp_dir (sort keys %g_exp_dir_meta_data)
        {
          if ($g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"})
            {
              $selected_archive      = $exp_dir;
              $archive_dir_not_empty = $FALSE;
              $archive_dir_selected  = $TRUE;
              $selected_archive_has_elf_format = $FALSE;
              last;
            }
        }
    }

  $msg  = "experiment $selected_archive has been selected for";
  $msg .= " archive analysis";
  gp_message ("debug", $subr_name, $msg);
  $msg  = "this archive is";
  $msg .= $archive_dir_not_empty ? " not empty" : " empty";
  gp_message ("debug", $subr_name, $msg);
  $msg  = "this archive is";
  $msg .= $selected_archive_has_elf_format ? " in" : " not in";
  $msg .= " ELF format";
  gp_message ("debug", $subr_name, $msg);
#------------------------------------------------------------------------------
# Get the size of the hash that contains the archived files.
#------------------------------------------------------------------------------
##  $NO_OF_FILES_IN_ARCHIVE = scalar (keys %ARCHIVES_FILES);

  $no_of_files_in_selected_archive =
	     $g_exp_dir_meta_data{$selected_archive}{"no_of_files_in_archive"};

  $msg  = "number of files in archive $selected_archive is";
  $msg .= " " . $no_of_files_in_selected_archive;
  gp_message ("debug", $subr_name, $msg);

  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      my $is_empty = $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"};
      $msg  = "archive directory $exp_dir/archives is";
      $msg .= $is_empty ? " empty" : " not empty";
      gp_message ("debug", $subr_name, $msg);
    }
  for my $exp_dir (sort keys %g_exp_dir_meta_data)
    {
      if (not $g_exp_dir_meta_data{$exp_dir}{"archive_is_empty"})
        {
          for my $object (sort keys
			%{$g_exp_dir_meta_data{$exp_dir}{"archive_files"}})
            {
              $msg  = $exp_dir . " " . $object . " ";
              $msg .=
		$g_exp_dir_meta_data{$exp_dir}{'archive_files'}{$object};
              gp_message ("debug", $subr_name, $msg);
            }
        }
    }

  return ($archive_dir_not_empty, $selected_archive, \%elf_rats);

} #-- End of subroutine check_validity_exp_dirs

#------------------------------------------------------------------------------
# Color the string and optionally mark it boldface.
#
# For supported colors, see:
# https://www.w3schools.com/colors/colors_names.asp
#------------------------------------------------------------------------------
sub color_string
{
  my $subr_name = get_my_name ();

  my ($input_string, $boldface, $color) = @_;

  my $colored_string;

  $colored_string = "<font color='" . $color . "'>";

  if ($boldface)
    {
      $colored_string .= "<b>";
    }

  $colored_string .= $input_string;

  if ($boldface)
    {
      $colored_string .= "</b>";
    }
  $colored_string .= "</font>";

  return ($colored_string);

} #-- End of subroutine color_string

#------------------------------------------------------------------------------
# Generate the array with the info on the experiment(s).
#------------------------------------------------------------------------------
sub create_exp_info
{
  my $subr_name = get_my_name ();

  my ($experiment_dir_list_ref, $experiment_data_ref) = @_;

  my @experiment_dir_list = @{ $experiment_dir_list_ref };
  my @experiment_data     = @{ $experiment_data_ref };

  my @experiment_stats_html = ();
  my $experiment_stats_line;
  my $msg;
  my $plural;

  $plural = ($#experiment_dir_list > 0) ? "s:" : ":";

  $experiment_stats_line  = "<h3>\n";
  $experiment_stats_line .= "Full pathnames to the input experiment";
  $experiment_stats_line .= $plural . "\n";
  $experiment_stats_line .= "</h3>\n";
  $experiment_stats_line .= "<pre>\n";

  for my $i (0 .. $#experiment_dir_list)
    {
      $experiment_stats_line .= $experiment_dir_list[$i] . " (" ;
      $experiment_stats_line .= $experiment_data[$i]{"start_date"} . ")\n";
    }
  $experiment_stats_line .= "</pre>\n";

  push (@experiment_stats_html, $experiment_stats_line);

  $msg = "experiment_stats_line = " . $experiment_stats_line;
  gp_message ("debugXL", $subr_name, $msg);

  return (\@experiment_stats_html);

} #-- End of subroutine create_exp_info

#------------------------------------------------------------------------------
# Trivial function to generate a tag.  This has been made a function to ensure
# consistency creating tags and also make it easier to change them.
#------------------------------------------------------------------------------
sub create_function_tag
{
  my $subr_name = get_my_name ();

  my ($tag_id) = @_;

  my $function_tag = "function_tag_" . $tag_id;

  return ($function_tag);

} #-- End of subroutine create_function_tag

#------------------------------------------------------------------------------
# Generate and return a string with the credits.  Note that this also ends
# the HTML formatting controls.
#------------------------------------------------------------------------------
sub create_html_credits
{
  my $subr_name = get_my_name ();

  my $msg;
  my $the_date;

  my @months = qw (January February March April May June July
		   August September October November December);

  my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
								localtime ();

  $year += 1900;

  $the_date = $months[$mon] . " " . $mday . ", " . $year;

  $msg  = "<i>\n";
  $msg .= "Output generated by the $driver_cmd command ";
  $msg .= "on $the_date ";
  $msg .= "(GNU binutils version " . $binutils_version . ")";
  $msg .= "\n";
  $msg .= "</i>";

  gp_message ("debug", $subr_name, "the date = $the_date");

  return (\$msg);

} #-- End of subroutine create_html_credits

#------------------------------------------------------------------------------
# Generate a string that contains all the necessary HTML header information,
# plus a title.
#
# See also https://www.w3schools.com for the details on the features used.
#------------------------------------------------------------------------------
sub create_html_header
{
  my $subr_name = get_my_name ();

  my ($title_ref) = @_;

   my $title = ${ $title_ref };

  my $LANG = $g_locale_settings{"LANG"};
  my $background_color = $g_html_color_scheme{"background_color_page"};

  my $html_header;

  $html_header  = "<!DOCTYPE html public \"-//w3c//dtd html 3.2//en\">\n";
  $html_header .= "<html lang=\"$LANG\">\n";
  $html_header .= "<head>\n";
  $html_header .= "<meta http-equiv=\"content-type\"";
  $html_header .= " content=\"text/html; charset=iso-8859-1\">\n";
  $html_header .= "<title>" . $title . "</title>\n";
  $html_header .= "</head>\n";
  $html_header .= "<body lang=\"$LANG\" bgcolor=". $background_color . ">\n";
  $html_header .= "<style>\n";
  $html_header .= "div.left {\n";
  $html_header .= "text-align: left;\n";
  $html_header .= "}\n";
  $html_header .= "div.right {\n";
  $html_header .= "text-align: right;\n";
  $html_header .= "}\n";
  $html_header .= "div.center {\n";
  $html_header .= "text-align: center;\n";
  $html_header .= "}\n";
  $html_header .= "div.justify {\n";
  $html_header .= "text-align: justify;\n";
  $html_header .= "}\n";
  $html_header .= "</style>";

  return (\$html_header);

} #-- End of subroutine create_html_header

#------------------------------------------------------------------------------
# Create a complete table.
#------------------------------------------------------------------------------
sub create_table
{
  my $subr_name = get_my_name ();

  my ($experiment_data_ref, $table_definition_ref) = @_;

  my @experiment_data  = @{ $experiment_data_ref };
  my @table_definition = @{ $table_definition_ref };

  my @html_exp_table_data = ();
  my $html_header_line;
  my $html_table_line;
  my $html_end_table;

  $html_header_line = ${ create_table_header_exp (\@experiment_data) };

  push (@html_exp_table_data, $html_header_line);

  for my $i (sort keys @table_definition)
    {
      $html_table_line = ${
		create_table_entry_exp (\$table_definition[$i]{"name"},
					\$table_definition[$i]{"key"},
					\@experiment_data) };
      push (@html_exp_table_data, $html_table_line);

      my $msg = "i = $i html_table_line = $html_table_line";
      gp_message ("debugXL", $subr_name, $msg);
    }

  $html_end_table  = "</table>\n";
  push (@html_exp_table_data, $html_end_table);

  return (\@html_exp_table_data);

} #-- End of subroutine create_table

#------------------------------------------------------------------------------
# Create one row for the table with experiment info.
#------------------------------------------------------------------------------
sub create_table_entry_exp
{
  my $subr_name = get_my_name ();

  my ($entry_name_ref, $key_ref, $experiment_data_ref) = @_;

  my $entry_name       = ${ $entry_name_ref };
  my $key              = ${ $key_ref };
  my @experiment_data  = @{ $experiment_data_ref };

  my $html_line;
  my $msg;

  $msg = "entry_name = $entry_name key = $key";
  gp_message ("debugXL", $subr_name, $msg);

##  $html_line  = "<tr><div class=\"left\"><td><b>&nbsp; ";
  $html_line  = "<tr><div class=\"right\"><td><b>&nbsp; ";
  $html_line .= $entry_name;
  $html_line .= " &nbsp;</b></td>";
  for my $i (sort keys @experiment_data)
    {
      if (exists ($experiment_data[$i]{$key}))
        {
          $html_line .= "<td>&nbsp; " . $experiment_data[$i]{$key};
          $html_line .= " &nbsp;</td>";
        }
      else
        {
          $msg = "experiment_data[$i]{$key} does not exist";
##          gp_message ("assertion", $subr_name, $msg);
# TBD: warning or error?
          gp_message ("warning", $subr_name, $msg);
        }
    }
  $html_line .= "</div></tr>\n";

  gp_message ("debugXL", $subr_name, "return html_line = $html_line");

  return (\$html_line);

} #-- End of subroutine create_table_entry_exp

#------------------------------------------------------------------------------
# Create the table header for the experiment info.
#------------------------------------------------------------------------------
sub create_table_header_exp
{
  my $subr_name = get_my_name ();

  my ($experiment_data_ref) = @_;

  my @experiment_data = @{ $experiment_data_ref };
  my $html_header_line;
  my $msg;

  $html_header_line  = "<style>\n";
  $html_header_line .= "table, th, td {\n";
  $html_header_line .= "border: 1px solid black;\n";
  $html_header_line .= "border-collapse: collapse;\n";
  $html_header_line .= "}\n";
  $html_header_line .= "</style>\n";
  $html_header_line .= "</pre>\n";
  $html_header_line .= "<table>\n";
  $html_header_line .= "<tr><div class=\"center\"><th></th>";

  for my $i (sort keys @experiment_data)
    {
      $html_header_line .= "<th>&nbsp; Experiment ID ";
      $html_header_line .= $experiment_data[$i]{"exp_id"} . "&nbsp;</th>";
    }
  $html_header_line .= "</div></tr>\n";

  $msg = "html_header_line = " . $html_header_line;
  gp_message ("debugXL", $subr_name, $msg);

  return (\$html_header_line);

} #-- End of subroutine create_table_header_exp

#------------------------------------------------------------------------------
# Handle where the output should go. If needed, a directory is created where
# the results will go.
#------------------------------------------------------------------------------
sub define_the_output_directory
{
  my $subr_name = get_my_name ();

  my ($define_new_output_dir, $overwrite_output_dir) = @_;

  my $msg;
  my $outputdir;

#------------------------------------------------------------------------------
# If neither -o or -O are set, find the next number to be used in the name for
# the default output directory.
#------------------------------------------------------------------------------
  if ((not $define_new_output_dir) and (not $overwrite_output_dir))
    {
      my $dir_id = 1;
      while (-d "er.".$dir_id.".html")
        { $dir_id++; }
      $outputdir = "er.".$dir_id.".html";
    }

  if (-d $outputdir)
    {
#------------------------------------------------------------------------------
# The -o option is used, but the directory already exists.
#------------------------------------------------------------------------------
      if ($define_new_output_dir)
        {
          $msg = "directory $outputdir already exists";
          gp_message ("error", $subr_name, $msg);
          $g_total_error_count++;

          $msg  =  "use the -O/--overwrite option to overwrite an existing";
          $msg .= " directory";
          gp_message ("abort", $subr_name, $msg);
        }
#------------------------------------------------------------------------------
# This is a bit risky, so we proceed with caution. The output directory exists,
# but it is okay to overwrite it. It is removed here and created again below.
#------------------------------------------------------------------------------
      elsif ($overwrite_output_dir)
        {
          my $target_cmd = $g_mapped_cmds{"rm"};
          my $rm_output  = qx ($target_cmd -rf $outputdir);
          my $error_code = ${^CHILD_ERROR_NATIVE};
          if ($error_code != 0)
            {
              gp_message ("error", $subr_name, $rm_output);
              $msg = "fatal error when trying to remove " . $outputdir;
              gp_message ("abort", $subr_name, $msg);
            }
          else
            {
              $msg = "directory $outputdir has been removed";
              gp_message ("debug", $subr_name, $msg);
            }
        }
    }
#------------------------------------------------------------------------------
# When we get here, the fatal scenarios have been cleared and the name for
# $outputdir is known. Time to create it.
#------------------------------------------------------------------------------
  if (mkdir ($outputdir, 0777))
    {
      $msg = "created output directory " . $outputdir;
      gp_message ("debug", $subr_name, $msg);
    }
  else
    {
      $msg = "a fatal problem occurred when creating directory " . $outputdir;
      gp_message ("abort", $subr_name, $msg);
    }

  return ($outputdir);

} #-- End of subroutine define_the_output_directory

#------------------------------------------------------------------------------
# Return the virtual address for the load object.
#
# Note that at this point, $elf_arch is known to be supported.
#
# TBD: Duplications?
#------------------------------------------------------------------------------
sub determine_base_va_address
{
  my $subr_name = get_my_name ();

  my ($executable_name, $base_va_executable, $loadobj, $routine) = @_;

  my $msg;
  my $name_loadobject;
  my $base_va_address;

  $msg = "base_va_executable = " . $base_va_executable;
  gp_message ("debugXL", $subr_name, $msg);
  $msg = "loadobj            = " . $loadobj;
  gp_message ("debugXL", $subr_name, $msg);
  $msg = "routine            = " . $routine;
  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Strip the pathname from the load object name.
#------------------------------------------------------------------------------
  $name_loadobject = get_basename ($loadobj);

#------------------------------------------------------------------------------
# If the load object is the executable, return the base address determined
# earlier.  Otherwise return 0x0.  Note that I am not sure if this is always
# the right thing to do, but for .so files it seems to work out fine.
#------------------------------------------------------------------------------
  if ($name_loadobject eq $executable_name)
    {
      $base_va_address = $base_va_executable;
    }
  else
    {
      $base_va_address = "0x0";
    }

  my $decimal_address = bigint::hex ($base_va_address);

  $msg  = "return base_va_address = $base_va_address";
  $msg .= " (decimal: $decimal_address)";
  gp_message ("debugXL", $subr_name, $msg);

  return ($base_va_address);

} #-- End of subroutine determine_base_va_address

#------------------------------------------------------------------------------
# Now that we know the map.xml file(s) are present, we can scan these and get
# the required information.
#------------------------------------------------------------------------------
sub determine_base_virtual_address
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref) = @_;

  my @exp_dir_list   = @{ $exp_dir_list_ref };

  my $executable_name;
  my $full_path_exec;
  my $msg;
  my $path_to_map_file;
  my $va_executable_in_hex;

  for my $exp_dir (keys %g_exp_dir_meta_data)
    {
      $path_to_map_file  = $g_exp_dir_meta_data{$exp_dir}{"directory_path"};
      $path_to_map_file .= $exp_dir;
      $path_to_map_file .= "/map.xml";

      ($full_path_exec, $executable_name, $va_executable_in_hex) =
				extract_info_from_map_xml ($path_to_map_file);

      $g_exp_dir_meta_data{$exp_dir}{"full_path_exec"} = $full_path_exec;
      $g_exp_dir_meta_data{$exp_dir}{"exec_name"}      = $executable_name;
      $g_exp_dir_meta_data{$exp_dir}{"va_base_in_hex"} = $va_executable_in_hex;

      $msg = "exp_dir              = " . $exp_dir;
      gp_message ("debug", $subr_name, $msg);
      $msg = "full_path_exece      = " . $full_path_exec;
      gp_message ("debug", $subr_name, $msg);
      $msg = "executable_name      = " . $executable_name;
      gp_message ("debug", $subr_name, $msg);
      $msg = "va_executable_in_hex = " . $va_executable_in_hex;
      gp_message ("debug", $subr_name, $msg);
    }

  return (0);

} #-- End of subroutine determine_base_virtual_address

#------------------------------------------------------------------------------
# Determine whether the decimal separator is a point or a comma.
#------------------------------------------------------------------------------
sub determine_decimal_separator
{
  my $subr_name = get_my_name ();

  my $cmd_output;
  my $convert_to_dot;
  my $decimal_separator;
  my $error_code;
  my $field;
  my $ignore_count;
  my @locale_info = ();
  my $msg;
  my $target_cmd;
  my $target_found;

  my $default_decimal_separator = "\\.";

  $target_cmd  = $g_mapped_cmds{locale} . " -k LC_NUMERIC";
  ($error_code, $cmd_output) = execute_system_cmd ($target_cmd);

  if ($error_code != 0)
#------------------------------------------------------------------------------
# This is unlikely to happen, but you never know.  To reduce the nesting level,
# return right here in case of an error.
#------------------------------------------------------------------------------
    {
      $msg = "failure to execute the command " . $target_cmd;
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;

      $convert_to_dot = $TRUE;

      return ($error_code, $default_decimal_separator, $convert_to_dot);
    }

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Scan the locale info and search for the target line of the form
# decimal_point="<target>" where <target> is either a dot, or a comma.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Split the output into the different lines and scan for the line we need.
#------------------------------------------------------------------------------
  @locale_info  = split ("\n", $cmd_output);
  $target_found = $FALSE;
  for my $line (@locale_info)
    {
      chomp ($line);
      $msg = "line from locale_info = " . $line;
      gp_message ("debug", $subr_name, $msg);

      if ($line =~ /decimal_point=/)
        {

#------------------------------------------------------------------------------
# Found the target line. Split this line to get the value field.
#------------------------------------------------------------------------------
          my @split_line = split ("=", $line);

#------------------------------------------------------------------------------
# There should be 2 fields. If not, something went wrong.
#------------------------------------------------------------------------------
          if (scalar @split_line != 2)
            {
#     if (scalar @split_line == 2) {
#        $target_found    = $FALSE;
#------------------------------------------------------------------------------
# Remove the newline before printing the variables.
#------------------------------------------------------------------------------
              $ignore_count = chomp ($line);
              $ignore_count = chomp (@split_line);

              $msg  = "line $line matches the search, but the decimal";
              $msg .= " separator has the wrong format";
              gp_message ("warning", $subr_name, $msg);
              $msg  = "the splitted line is [@split_line] and does not";
              $msg .= " contain 2 fields";
              gp_message ("warning", $subr_name, $msg);
              $msg  = "the default decimal separator will be used";
              gp_message ("warning", $subr_name, $msg);

              $g_total_warning_count++;
            }
          else
            {
#------------------------------------------------------------------------------
# We know there are 2 fields and the second one has the decimal point.
#------------------------------------------------------------------------------
              $msg = "split_line[1] = " . $split_line[1];
              gp_message ("debug", $subr_name, $msg);

              chomp ($split_line[1]);
              $field = $split_line[1];

              if (length ($field) != 3)
#------------------------------------------------------------------------------
# The field still includes the quotes.  Check if the string has length 3, which
# should be the case, but if not, we flag an error.  The error code is set such
# that the callee will know a problem has occurred.
#------------------------------------------------------------------------------
                {
                  $msg  = "unexpected output from the $target_cmd command:";
                  $msg .= " " . $field;
                  gp_message ("error", $subr_name, $msg);

                  $g_total_error_count++;

                  $error_code = 1;
                  last;
                }

              $msg = "field = ->$field<-";
              gp_message ("debug", $subr_name, $msg);

              if (($field eq "\".\"") or ($field eq "\",\""))
#------------------------------------------------------------------------------
# Found the separator.  Capture the character between the quotes.
#------------------------------------------------------------------------------
                {
                  $target_found      = $TRUE;
                  $decimal_separator = substr ($field,1,1);
                  $msg  = "decimal_separator = $decimal_separator--end";
                  $msg .= " skip remainder of loop";
                  gp_message ("debug", $subr_name, $msg);
                  last;
                }
            }
        }
    }
  if (not $target_found)
    {
      $decimal_separator = $default_decimal_separator;
      $msg  = "cannot determine the decimal separator";
      $msg .= " - use the default " . $decimal_separator;
      gp_message ("warning", $subr_name, $msg);

      $g_total_warning_count++;
    }

  if ($decimal_separator ne ".")
    {
      $convert_to_dot = $TRUE;
    }
  else
    {
      $convert_to_dot = $FALSE;
    }

  $decimal_separator = "\\".$decimal_separator;
  $g_locale_settings{"decimal_separator"} = $decimal_separator;
  $g_locale_settings{"convert_to_dot"}    = $convert_to_dot;

  return ($error_code, $decimal_separator, $convert_to_dot);

} #-- End of subroutine determine_decimal_separator

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub dump_function_info
{
  my $subr_name = get_my_name ();

  my ($function_info_ref, $name) = @_;

  my %function_info = %{$function_info_ref};
  my $kip;
  my $msg;

  $msg = "function_info for " . $name;
  gp_message ("debug", $subr_name, $msg);

  $kip = 0;
  for my $farray ($function_info{$name})
    {
      for my $elm (@{$farray})
        {
          $msg = $kip . ": routine = " . ${$elm}{"routine"};
          gp_message ("debug", $subr_name, $msg);
          for my $key (sort keys %{$elm})
            {
              if ($key eq "routine")
                {
                  next;
                }
              $msg = $kip . ": $key = " . ${$elm}{$key};
              gp_message ("debug", $subr_name, $msg);
            }
          $kip++;
        }
    }

  return (0);

} #-- End of subroutine dump_function_info

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub elf_phdr
{
  my $subr_name = get_my_name ();

  my ($elf_loadobjects_found, $elf_arch, $loadobj, $routine,
      $ARCHIVES_MAP_NAME, $ARCHIVES_MAP_VADDR, $elf_rats_ref) = @_;

  my %elf_rats = %{$elf_rats_ref};

  my $msg;
  my $return_value;

#------------------------------------------------------------------------------
# TBD. Quick check. Can be moved up the call tree.
#------------------------------------------------------------------------------
    if ( $elf_arch ne "Linux" )
      {
        $msg = $elf_arch . " is not a supported OS";
        gp_message ("error", $subr_name, $msg);
        $g_total_error_count++;
        gp_message ("abort", $subr_name, $g_abort_msg);
      }

#------------------------------------------------------------------------------
# TBD: This should not be in a loop over $loadobj and only use the executable.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# TBD: $routine is not really used in these subroutines. Is this a bug?
#------------------------------------------------------------------------------
  if ($elf_loadobjects_found)
    {
      gp_message ("debugXL", $subr_name, "calling elf_phdr_usual");
      $return_value = elf_phdr_usual ($elf_arch,
				      $loadobj,
				      $routine,
				      \%elf_rats);
    }
  else
    {
      gp_message ("debugXL", $subr_name, "calling elf_phdr_sometimes");
      $return_value = elf_phdr_sometimes ($elf_arch,
					  $loadobj,
					  $routine,
					  $ARCHIVES_MAP_NAME,
					  $ARCHIVES_MAP_VADDR);
    }

  gp_message ("debug", $subr_name, "the return value = $return_value");

  if (not $return_value)
    {
      $msg = "need to handle a return value of FALSE";
      gp_message ("error", $subr_name, $msg);
      $g_total_error_count++;
      gp_message ("abort", $subr_name, $g_abort_msg);
    }

  return ($return_value);

} #-- End of subroutine elf_phdr

#------------------------------------------------------------------------------
# Return the virtual address for the load object.
#------------------------------------------------------------------------------
sub elf_phdr_sometimes
{
  my $subr_name = get_my_name ();

  my ($elf_arch, $loadobj, $routine, $ARCHIVES_MAP_NAME,
      $ARCHIVES_MAP_VADDR) = @_;

  my $arch_uname_s = $local_system_config{"kernel_name"};
  my $arch_uname   = $local_system_config{"processor"};
  my $arch         = $g_arch_specific_settings{"arch"};

  gp_message ("debug", $subr_name, "arch_uname_s = $arch_uname_s");
  gp_message ("debug", $subr_name, "arch_uname   = $arch_uname");
  gp_message ("debug", $subr_name, "arch         = $arch");

  my $cmd_output;
  my $command_string;
  my $error_code;
  my $msg;
  my $target_cmd;

  my $line;
  my $blo;

  my $elf_offset;
  my $i;
  my @foo;
  my $foo;
  my $foo1;
  my $p_vaddr;
  my $rc;
  my $archives_file;
  my $loadobj_SAVE;
  my $Offset;
  my $VirtAddr;
  my $PhysAddr;
  my $FileSiz;
  my $MemSiz;
  my $Flg;
  my $Align;

  if ($ARCHIVES_MAP_NAME eq $blo)
    {
      return ($ARCHIVES_MAP_VADDR);
    }
  else
    {
      return ($FALSE);
    }

  if ($arch_uname_s ne $elf_arch)
    {
#------------------------------------------------------------------------------
# We are masquerading between systems, must leave
#------------------------------------------------------------------------------
      $msg = "masquerading arch_uname_s->$arch_uname_s elf_arch->$elf_arch";
      gp_message ("debug", $subr_name, $msg);
      return ($FALSE);
    }

  if ($loadobj eq "DYNAMIC_FUNCTIONS")
#------------------------------------------------------------------------------
# Linux vDSO, leave for now
#------------------------------------------------------------------------------
    {
      return ($FALSE);
    }

# TBD: STILL NEEDED??!!

  $loadobj_SAVE = $loadobj;

  $blo = get_basename ($loadobj);
  gp_message ("debug", $subr_name, "loadobj = $loadobj");
  gp_message ("debug", $subr_name, "blo     = $blo");
  gp_message ("debug", $subr_name, "ARCHIVES_MAP_NAME  = $ARCHIVES_MAP_NAME");
  gp_message ("debug", $subr_name, "ARCHIVES_MAP_VADDR = $ARCHIVES_MAP_VADDR");
  if ($ARCHIVES_MAP_NAME eq $blo)
    {
      return ($ARCHIVES_MAP_VADDR);
    }
  else
    {
      return ($FALSE);
    }

} #-- End of subroutine elf_phdr_sometimes

#------------------------------------------------------------------------------
# Return the virtual address for the load object.
#
# Note that at this point, $elf_arch is known to be supported.
#------------------------------------------------------------------------------
sub elf_phdr_usual
{
  my $subr_name = get_my_name ();

  my ($elf_arch, $loadobj, $routine, $elf_rats_ref) = @_;

  my %elf_rats = %{$elf_rats_ref};

  my $load_long_regex;
  $load_long_regex     = '^\s+LOAD\s+(\S+)\s+(\S+)\s+(\S+)';
  $load_long_regex    .= '\s+(\S+)\s+(\S+)\s+(R\sE)\s+(\S+)$';
  my $load_short_regex = '^\s+LOAD\s+(\S+)\s+(\S+)\s+(\S+)$';
  my $re_regex         = '^\s+(\S+)\s+(\S+)\s+(R\sE)\s+(\S+)$';

  my $return_code;
  my $cmd_output;
  my $target_cmd;
  my $command_string;
  my $error_code;
  my $error_code1;
  my $error_code2;
  my $msg;

  my ($elf_offset, $loadobjARC);
  my ($i, @foo, $foo, $foo1, $p_vaddr, $rc);
  my ($Offset, $VirtAddr, $PhysAddr, $FileSiz, $MemSiz, $Flg, $Align);

  my $arch_uname_s = $local_system_config{"kernel_name"};

  $msg = "elf_arch = $elf_arch loadobj = $loadobj routine = $routine";
  gp_message ("debug", $subr_name, $msg);

  my ($base, $ignore_value, $ignore_too) = fileparse ($loadobj);

  $msg = "base = $base " . basename ($loadobj);
  gp_message ("debug", $subr_name, $msg);

  if ($elf_arch eq "Linux")
    {
      if ($arch_uname_s ne $elf_arch)
        {
#------------------------------------------------------------------------------
# We are masquerading between systems, must leave.
# Maybe we could use ELF_RATS
#------------------------------------------------------------------------------
          $msg  = "masquerading arch_uname_s->" . $arch_uname_s;
          $msg .= " elf_arch->" . $elf_arch;
          gp_message ("debug", $subr_name, $msg);

          return ($FALSE);
        }
      if ($loadobj eq "DYNAMIC_FUNCTIONS")
        {
#------------------------------------------------------------------------------
# Linux vDSO, leave for now
#------------------------------------------------------------------------------
          gp_message ("debug", $subr_name, "early return: loadobj = $loadobj");
          return ($FALSE);
        }

      $target_cmd     = $g_mapped_cmds{"readelf"};
      $command_string = $target_cmd . " -l " . $loadobj . " 2>/dev/null";

      ($error_code1, $cmd_output) = execute_system_cmd ($command_string);

      $msg = "executed command_string = " . $command_string;
      gp_message ("debug", $subr_name, $msg);
      $msg = "cmd_output = " . $cmd_output;
      gp_message ("debug", $subr_name, $msg);

      if ($error_code1 != 0)
        {
          gp_message ("debug", $subr_name, "call failure for $command_string");
#------------------------------------------------------------------------------
# e.g. $loadobj->/usr/lib64/libc-2.17.so
#------------------------------------------------------------------------------
          $loadobjARC = get_basename ($loadobj);
          gp_message ("debug", $subr_name, "seek elf_rats for $loadobjARC");

          if (exists ($elf_rats{$loadobjARC}))
            {
              my $elfoid;
              $elfoid  = $elf_rats{$loadobjARC}[1] . "/archives/";
              $elfoid .= $elf_rats{$loadobjARC}[0];
              $target_cmd     = $g_mapped_cmds{"readelf"};
              $command_string = $target_cmd . "-l " . $elfoid . " 2>/dev/null";
              ($error_code2, $cmd_output) =
					execute_system_cmd ($command_string);

              if ($error_code2 != 0)
                {
                  $msg = "call failure for " . $command_string;
                  gp_message ("error", $subr_name, $msg);
                  $g_total_error_count++;
                  gp_message ("abort", $subr_name, $g_abort_msg);
                }
              else
                {
                  $msg = "executed command_string = " . $command_string;
                  gp_message ("debug", $subr_name, $msg);
                  $msg = "cmd_output = " . $cmd_output;
                  gp_message ("debug", $subr_name, $msg);
                }
            }
          else
            {
              $msg =  "elf_rats{$loadobjARC} does not exist";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
#------------------------------------------------------------------------------
# Example output of "readelf -l" on Linux:
#
# Elf file type is EXEC (Executable file)
# Entry point 0x4023a0
# There are 11 program headers, starting at offset 64
#
# Program Headers:
#   Type           Offset             VirtAddr           PhysAddr
#                  FileSiz            MemSiz              Flags  Align
#   PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
#                  0x0000000000000268 0x0000000000000268  R      8
#   INTERP         0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
#                  0x000000000000001c 0x000000000000001c  R      1
#       [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
#   LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
#                  0x0000000000001310 0x0000000000001310  R      1000
#   LOAD           0x0000000000002000 0x0000000000402000 0x0000000000402000
#                  0x0000000000006515 0x0000000000006515  R E    1000
#   LOAD           0x0000000000009000 0x0000000000409000 0x0000000000409000
#                  0x000000000006f5a8 0x000000000006f5a8  R      1000
#   LOAD           0x0000000000078dc8 0x0000000000479dc8 0x0000000000479dc8
#                  0x000000000000047c 0x0000000000000f80  RW     1000
#   DYNAMIC        0x0000000000078dd8 0x0000000000479dd8 0x0000000000479dd8
#                  0x0000000000000220 0x0000000000000220  RW     8
#   NOTE           0x00000000000002c4 0x00000000004002c4 0x00000000004002c4
#                  0x0000000000000044 0x0000000000000044  R      4
#   GNU_EH_FRAME   0x00000000000777f4 0x00000000004777f4 0x00000000004777f4
#                  0x000000000000020c 0x000000000000020c  R      4
#   GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
#                  0x0000000000000000 0x0000000000000000  RW     10
#   GNU_RELRO      0x0000000000078dc8 0x0000000000479dc8 0x0000000000479dc8
#                  0x0000000000000238 0x0000000000000238  R      1
#
#  Section to Segment mapping:
#   Segment Sections...
#    00
#    01     .interp
#    02     .interp .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym
#           .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
#    03     .init .plt .text .fini
#    04     .rodata .eh_frame_hdr .eh_frame
#    05     .init_array .fini_array .dynamic .got .got.plt .data .bss
#    06     .dynamic
#    07     .note.gnu.build-id .note.ABI-tag
#    08     .eh_frame_hdr
#    09
#    10     .init_array .fini_array .dynamic .got
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Analyze the ELF information and try to find the virtual address.
#
# Note that the information printed as part of LOAD needs to have "R E" in it.
# In the example output above, the return value would be "0x0000000000402000".
#
# We also need to distinguish two cases.  It could be that the output is on
# a single line, or spread over two lines:
#
#                 Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align
#  LOAD           0x000000 0x08048000 0x08048000 0x61b4ae 0x61b4ae R E 0x1000
# or 2 lines
#  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
#                 0x0000000000001010 0x0000000000001010  R E    200000
#------------------------------------------------------------------------------
      @foo = split ("\n",$cmd_output);
      for $i (0 .. $#foo)
        {
          $foo = $foo[$i];
          chomp ($foo);
          if ($foo =~ /$load_long_regex/)
            {
              $Offset   = $1;
              $VirtAddr = $2;
              $PhysAddr = $3;
              $FileSiz  = $4;
              $MemSiz   = $5;
              $Flg      = $6;
              $Align    = $7;

              $elf_offset = $VirtAddr;
              $msg = "single line version elf_offset = " . $elf_offset;
              gp_message ("debug", $subr_name, $msg);
              return ($elf_offset);
            }
          elsif ($foo =~ /$load_short_regex/)
            {
#------------------------------------------------------------------------------
# is it a two line version?
#------------------------------------------------------------------------------
              $Offset   = $1;
              $VirtAddr = $2; # maybe
              $PhysAddr = $3;
              if ($i != $#foo)
                {
                  $foo1 = $foo[$i + 1];
                  chomp ($foo1);
                  if ($foo1 =~ /$re_regex/)
                    {
                      $FileSiz  = $1;
                      $MemSiz   = $2;
                      $Flg      = $3;
                      $Align    = $4;
                      $elf_offset = $VirtAddr;
                      $msg = "two line version elf_offset = " . $elf_offset;
                      gp_message ("debug", $subr_name, $msg);
                      return ($elf_offset);
                    }
                }
            }
        }
    }

} #-- End of subroutine elf_phdr_usual

#------------------------------------------------------------------------------
# Execute a system command.  In case of an error, a non-zero error code is
# returned.  It is upon the caller to decide what to do next.
#------------------------------------------------------------------------------
sub execute_system_cmd
{
  my $subr_name = get_my_name ();

  my ($target_cmd) = @_;

  my $cmd_output;
  my $error_code;
  my $msg;

  chomp ($target_cmd);

  $cmd_output = qx ($target_cmd);
  $error_code = ${^CHILD_ERROR_NATIVE};

  if ($error_code != 0)
    {
      chomp ($cmd_output);
      $msg = "failure executing command " . $target_cmd;
      gp_message ("error", $subr_name, $msg);
      $msg = "error code = " . $error_code;
      gp_message ("error", $subr_name, $msg);
      $msg = "cmd_output = " . $cmd_output;

      gp_message ("error", $subr_name, $msg);
      $g_total_error_count++;
    }
  else
    {
      $msg = "executed command " . $target_cmd;
      gp_message ("debugXL", $subr_name, $msg);
    }

  return ($error_code, $cmd_output);

} #-- End of subroutine execute_system_cmd

#------------------------------------------------------------------------------
# Scan the input file, which should be a gprofng generated map.xml file, and
# extract the relevant information.
#------------------------------------------------------------------------------
sub extract_info_from_map_xml
{
  my $subr_name = get_my_name ();

  my ($input_map_xml_file) = @_;

  my $map_xml_regex;
  $map_xml_regex  = '<event kind="map"\s.*';
  $map_xml_regex .= 'vaddr="0x([0-9a-fA-F]+)"\s+.*';
  $map_xml_regex .= 'foffset="\+*0x([0-9a-fA-F]+)"\s.*';
  $map_xml_regex .= 'modes="0x([0-9]+)"\s.*';
  $map_xml_regex .= 'name="(.*)".*>$';

  my $extracted_information;
  my $input_line;
  my $vaddr;
  my $foffset;
  my $msg;
  my $modes;
  my $name_path;
  my $name;

  my $full_path_exec;
  my $executable_name;
  my $result_VA;
  my $va_executable_in_hex;

  $msg = " - unable to open file $input_map_xml_file for reading:";
  open (MAP_XML, "<", $input_map_xml_file)
    or die ($subr_name . $msg . " " . $!);

  $msg = "opened file $input_map_xml_file for reading";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Scan the file.  We need to find the name of the executable with the mode set
# to 0x005.  For this entry we have to capture the name, the mode, the virtual
# address and the offset.
#------------------------------------------------------------------------------
  $extracted_information = $FALSE;
  while (<MAP_XML>)
    {
      $input_line = $_;
      chomp ($input_line);

      $msg = "read input_line = $input_line";
      gp_message ("debug", $subr_name, $msg);

      if ($input_line =~  /^$map_xml_regex/)
        {
          $msg = "target line = $input_line";
          gp_message ("debug", $subr_name, $msg);

          $vaddr     = $1;
          $foffset   = $2;
          $modes     = $3;
          $name_path = $4;
          $name      = get_basename ($name_path);

          $msg  = "extracted vaddr = $vaddr foffset = $foffset";
          $msg .= " modes = $modes";
          gp_message ("debug", $subr_name, $msg);

          $msg = "extracted name_path = $name_path name = $name";
          gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# The base virtual address is calculated as vaddr-foffset.  Although Perl
# handles arithmetic in hex, we take the safe way here.  Maybe overkill, but
# I prefer to be safe than sorry in cases like this.
#------------------------------------------------------------------------------
          $full_path_exec   = $name_path;
          $executable_name  = $name;
          $result_VA        = bigint::hex ($vaddr) - bigint::hex ($foffset);
          $va_executable_in_hex = sprintf ("0x%016x", $result_VA);

##          $ARCHIVES_MAP_NAME  = $name;
##          $ARCHIVES_MAP_VADDR = $va_executable_in_hex;

          $msg = "result_VA            = $result_VA";
          gp_message ("debug", $subr_name, $msg);

          $msg = "va_executable_in_hex = $va_executable_in_hex";
          gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Stop reading when we found the correct entry.
#------------------------------------------------------------------------------
          if ($modes eq "005")
            {
              $extracted_information = $TRUE;
              last;
            }
        }
    } #-- End of while-loop

  if (not $extracted_information)
    {
      $msg  = "cannot find the necessary information in file";
      $msg .= " " . $input_map_xml_file;
      gp_message ("assertion", $subr_name, $msg);
    }

  $msg = "full_path_exec       = $full_path_exec";
  gp_message ("debug", $subr_name, $msg);
  $msg = "executable_name      = $executable_name";
  gp_message ("debug", $subr_name, $msg);
  $msg = "va_executable_in_hex = $va_executable_in_hex";
  gp_message ("debug", $subr_name, $msg);

  return ($full_path_exec, $executable_name, $va_executable_in_hex);

} #-- End of subroutine extract_info_from_map_xml

#------------------------------------------------------------------------------
# This routine analyzes the metric line and extracts the metric details.
# Example input: Exclusive Total CPU Time: e.%totalcpu
#------------------------------------------------------------------------------
sub extract_metric_specifics
{
  my $subr_name = get_my_name ();

  my ($metric_line) = @_;

  my $metric_description;
  my $metric_flavor;
  my $metric_visibility;
  my $metric_name;
  my $metric_spec;
  my $msg;

# Ruud   if (($metric =~ /\s*(.*):\s+(\S)((\.\S+)|(\+\S+))/) && !($metric =~/^Current/)){
  if (($metric_line =~ /\s*(.+):\s+([ei])([\.\+%]+)(\S*)/) and !($metric_line =~/^Current/))
    {
      $msg = "input line = " . $metric_line;
      gp_message ("debug", $subr_name, $msg);

      $metric_description = $1;
      $metric_flavor      = $2;
      $metric_visibility  = $3;
      $metric_name        = $4;

#------------------------------------------------------------------------------
# Although we have captured the metric visibility, the original code removes
# this from the name.  Since the structure is more complicated, the code is
# more tedious as well.  With our new approach we just leave the visibility
# out.
#------------------------------------------------------------------------------
#      $metric_spec        = $metric_flavor.$metric_visibility.$metric_name;

      $metric_spec        = $metric_flavor . "." . $metric_name;

#------------------------------------------------------------------------------
# From the original code:
#
# On x64 systems there are metrics which contain ~ (for example
# DC_access~umask=0 .  When er_print lists them, they come out
# as DC_access%7e%umask=0 (see 6530691).  Untill 6530691 is
# fixed, we need this.  Later we may need something else, or
# things may just work.
#------------------------------------------------------------------------------
#          $metric_spec=~s/\%7e\%/,/;
#          # remove % metric
#          print "DB: before \$metric_spec = $metric_spec\n";

#------------------------------------------------------------------------------
# TBD: I don't know why the "%" symbol is removed.
#------------------------------------------------------------------------------
#          $metric_spec =~ s/\%//;
#          print "DB: after  \$metric_spec = $metric_spec\n";

      $msg = "on return: metric_spec        = " . $metric_spec;
      gp_message ("debugM", $subr_name, $msg);
      $msg = "on return: metric_flavor      = " . $metric_flavor;
      gp_message ("debugM", $subr_name, $msg);
      $msg = "on return: metric_visibility  = " . $metric_visibility;
      gp_message ("debugM", $subr_name, $msg);
      $msg = "on return: metric_name        = " .  $metric_name;
      gp_message ("debugM", $subr_name, $msg);
      $msg = "on return: metric_description = " . $metric_description;
      gp_message ("debugM", $subr_name, $msg);

      return ($metric_spec, $metric_flavor, $metric_visibility,
              $metric_name, $metric_description);
    }
  else
    {
      return ("skipped", "void");
    }

} #-- End of subroutine extract_metric_specifics

#------------------------------------------------------------------------------
# Extract the option value(s) from the input array.  In case the number of
# values execeeds the specified limit, warning messages are printed.
#
# In case the option value is valid, g_user_settings is updated with this
# value and a value of TRUE is returned.  Otherwise the return value is FALSE.
#
# Note that not in all invocations of this subroutine, gp_message() is
# operational.  Only after the debug settings have been finalized, the
# messages are printed.
#
# This subroutine also generates warnings about multiple occurrences
# and the validity of the values.
#------------------------------------------------------------------------------
sub extract_option_value
{
  my $subr_name = get_my_name ();

  my ($option_dir_ref, $max_occurrences_ref, $internal_option_name_ref,
      $option_name_ref) = @_;

  my @option_dir           = @{ $option_dir_ref };
  my $max_occurrences      = ${ $max_occurrences_ref };
  my $internal_option_name = ${ $internal_option_name_ref };
  my $option_name          = ${ $option_name_ref };

  my $deprecated_option_used;
  my $excess_occurrences;
  my $msg;
  my $no_of_occurrences;
  my $no_of_warnings = 0;
  my $option_value   = "not set yet";
  my $option_value_missing;
  my $option_value_missing_ref;
  my $reset_blank_value;
  my $special_treatment = $FALSE;
  my $valid = $FALSE;
  my $valid_ref;

  if (@option_dir)
    {
      $no_of_occurrences = scalar (@option_dir);

      $msg = "option_name          = $option_name";
      gp_message ("debug", $subr_name, $msg);
      $msg = "internal_option_name = $internal_option_name";
      gp_message ("debug", $subr_name, $msg);
      $msg = "no_of_occurrences    = $no_of_occurrences";
      gp_message ("debug", $subr_name, $msg);

      $excess_occurrences = ($no_of_occurrences > $max_occurrences) ?
							$TRUE : $FALSE;

#------------------------------------------------------------------------------
# This is not supposed to happen, but just to be sure, there is a check.
#------------------------------------------------------------------------------
      if ($no_of_occurrences < 1)
        {
          $msg  = "the number of fields is $no_of_occurrences";
          $msg .= " - should at least be 1";
          gp_message ("assertion", $subr_name, $msg);
        }

#------------------------------------------------------------------------------
# For backward compatibility, we support the legacy "on" and "off" values for
# certain options.
#
# We also support the debug option without value.  In case no value is given,
# it is set to "on".
#
# Note that regardless of the value(s) in ARGV, internally we use the on/off
# setting.
#------------------------------------------------------------------------------
      if (($g_user_settings{$internal_option_name}{"data_type"} eq "onoff") or
          ($internal_option_name eq "debug"))
        {
          $msg = "enable special treatment of the option";
          gp_message ("debug", $subr_name, $msg);

          $special_treatment = $TRUE;
        }

#------------------------------------------------------------------------------
# Issue a warning if the same option occcurs more often than what is supported.
#------------------------------------------------------------------------------
      if ($excess_occurrences)
        {
          $msg = "multiple occurrences of the " . $option_name .
                 " option found:";

          gp_message ("debugM", $subr_name, $msg);

          gp_message ("warning", $subr_name, $g_html_new_line . $msg);
        }

#------------------------------------------------------------------------------
# Main loop over all the occurrences of the options.  This is a rather simple
# approach since only the last value seen will be accepted.
#
# To assist the user with troubleshooting, the values that are ignored will be
# checked for validity and a marker to this extent will be printed.
#
# NOTE:
# If an option may have multiple meaningful occurrences, this part needs to be
# revisited.
#------------------------------------------------------------------------------
      $deprecated_option_used = $FALSE;
      for my $key (keys @option_dir)
        {
          $option_value      = $option_dir[$key];
          $reset_blank_value = $FALSE;

#------------------------------------------------------------------------------
# For the "onoff" options, convert a blank value to "on".
#------------------------------------------------------------------------------
          if (($option_value eq "on") or ($option_value eq "off"))
            {
              if (($option_name eq "--verbose") or ($option_name eq "--quiet"))
                {
  		  $deprecated_option_used = $TRUE;
                }
            }

#------------------------------------------------------------------------------
# For the "onoff" options, convert a blank value to "on".
#------------------------------------------------------------------------------
          if ($special_treatment and ($option_value eq ""))
            {
              $option_value = "on";
              $reset_blank_value = $TRUE;

              $msg  = "reset option value for $option_name from blank";
              $msg .= " to \"on\"";
              gp_message ("debug", $subr_name, $msg);
            }

#------------------------------------------------------------------------------
# Check for the option value to be valid.  It may also happen that an option
# does not have a value, while it should have one.
#------------------------------------------------------------------------------
          ($valid_ref, $option_value_missing_ref) = check_and_set_user_option (
							$internal_option_name,
							$option_value);

          $valid                = ${ $valid_ref };
          $option_value_missing = ${ $option_value_missing_ref };

          $msg  = "option_value = $option_value";
          gp_message ("debug", $subr_name, $msg);
          $msg  = "after check_and_set_user_option: valid = $valid";
          $msg .= " option_value_missing = $option_value_missing";
          gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Generate warning messages, but if an option value is missing, it will also
# be considered to be a fatal error.
#------------------------------------------------------------------------------
          if ($excess_occurrences)
            {
              if ($option_value_missing)
                {
                  $msg  = "$option_name option - missing a value";
                }
              else
                {
#------------------------------------------------------------------------------
# A little trick to avoid user confusion.  Although we have set the internal
# value to "on", the user did not set this and so we print "" instead.
#------------------------------------------------------------------------------
                  if ($reset_blank_value)
                    {
                      $msg  = "$option_name option - value = \"\"";
                    }
                  else
                    {
                      $msg  = "$option_name option - value = $option_value";
                    }
                  $msg .= ($valid) ? " (valid value)" : " (invalid value)";
                }

              gp_message ("debug", $subr_name, $msg);
              gp_message ("warning", $subr_name, $msg);
            }

#------------------------------------------------------------------------------
# Check for the last occurrence of the option to be valid.  If it is not, it
# is a fatal error.
#------------------------------------------------------------------------------
          if ((not $valid) && ($key == $no_of_occurrences-1))
            {
              if ($option_value_missing)
                {
                  $msg = "the $option_name option requires a value";
                }
              else
                {
                  $msg  = "the value of $option_value for the $option_name";
                  $msg .= " option is invalid";
                }
              gp_message ("debug", $subr_name, $g_error_keyword . $msg);

              gp_message ("error", $subr_name, $msg);

              $g_total_error_count++;
            }
        }

#------------------------------------------------------------------------------
# Issue a warning if the same option occcurs more often than what is supported
# and warn the user that all but the last value will be ignored.
#------------------------------------------------------------------------------
      if ($excess_occurrences)
        {
          $msg = "all values but the last one shown above are ignored";

          gp_message ("debugM", $subr_name, $msg);
          gp_message ("warning", $subr_name, $msg);

          $g_total_warning_count++;
        }
    }

#------------------------------------------------------------------------------
# Issue a warning if the old on/off syntax is used still.
#------------------------------------------------------------------------------
  if ($deprecated_option_used)
    {
      $msg  = "<br>";
      $msg .= "the on/off syntax for option $option_name has been";
      $msg .= " deprecated";
      gp_message ("warning", $subr_name, $msg);

      $msg  = "this option acts like a switch now";
      gp_message ("warning", $subr_name, $msg);

      $msg  = "support for the old syntax may be terminated";
      $msg .= " in a future update";
      gp_message ("warning", $subr_name, $msg);

      $msg  = "please check the gprofng-display-html man page";
      $msg .= " for more details";
      gp_message ("warning", $subr_name, $msg);
      $g_total_warning_count++;
    }

  return (\$valid);

} #-- End of subroutine extract_option_value

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub extract_source_line_number
{
  my $subr_name = get_my_name ();

  my ($src_times_regex, $function_regex, $number_of_metrics, $input_line) = @_;

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $find_dot_regex = '\.';

  my @fields_in_line = ();
  my $hot_line;
  my $line_id;

#------------------------------------------------------------------------------
# To extract the source line number, we need to distinguish whether this is
# a line with, or without metrics.
#------------------------------------------------------------------------------
      @fields_in_line = split (" ", $input_line);
      if ( $input_line =~ /$src_times_regex/ )
        {
          $hot_line = $1;
          if ($hot_line eq "##")
#------------------------------------------------------------------------------
# The line id comes after the "##" symbol and the metrics.
#------------------------------------------------------------------------------
            {
              $line_id = $fields_in_line[$number_of_metrics+1];
            }
          else
#------------------------------------------------------------------------------
# The line id comes after the metrics.
#------------------------------------------------------------------------------
            {
              $line_id = $fields_in_line[$number_of_metrics];
            }
        }
      elsif ($input_line =~ /$function_regex/)
        {
          $line_id = "func";
        }
      else
#------------------------------------------------------------------------------
# The line id is the first non-blank element.
#------------------------------------------------------------------------------
        {
          $line_id = $fields_in_line[0];
        }
#------------------------------------------------------------------------------
# Remove the trailing dot.
#------------------------------------------------------------------------------
      $line_id =~ s/$find_dot_regex//;

   return ($line_id);

} #-- End of subroutine extract_source_line_number

#------------------------------------------------------------------------------
# Finalize the settings for the special options verbose, debug, warnings and
# quiet.
#------------------------------------------------------------------------------
sub finalize_special_options
{
  my $subr_name = get_my_name ();

  my $msg;

#------------------------------------------------------------------------------
# If quiet mode has been enabled, disable verbose, warnings and debug.
#------------------------------------------------------------------------------
  if ($g_quiet)
    {
      $g_user_settings{"verbose"}{"current_value"}    = "off";
      $g_user_settings{"nowarnings"}{"current_value"} = "on";
      $g_user_settings{"warnings"}{"current_value"}   = "off";
      $g_user_settings{"debug"}{"current_value"}      = "off";
      $g_debug    = $FALSE;
      $g_verbose  = $FALSE;
      $g_warnings = $FALSE;
      my $debug_off = "off";
      my $ignore_value = set_debug_size (\$debug_off);
    }
  else
    {
#------------------------------------------------------------------------------
# Disable output buffering if verbose, debug, and/or warnings are enabled.
#------------------------------------------------------------------------------
      if ($g_verbose or $g_debug or $g_warnings)
        {
          STDOUT->autoflush (1);

          $msg = "enabled autoflush for STDOUT";
          gp_message ("debug", $subr_name, $msg);
        }
#------------------------------------------------------------------------------
# If verbose and/or debug have been enabled, print a message.
#------------------------------------------------------------------------------
##      gp_message ("verbose", $subr_name, "verbose mode has been enabled");
##      gp_message ("debug",   $subr_name, "debug " . $g_debug_size_value . " mode has been enabled");
    }

  return (0);

} #-- End of subroutine finalize_special_options

#------------------------------------------------------------------------------
# For a give routine name and address, find the index into the
# function_info array
#------------------------------------------------------------------------------
sub find_index_in_function_info
{
  my $subr_name = get_my_name ();

  my ($routine_ref, $current_address_ref, $function_info_ref) = @_;

  my $routine = ${ $routine_ref };
  my $current_address = ${ $current_address_ref };
  my @function_info = @{ $function_info_ref };

  my $addr_offset;
  my $ref_index;

  gp_message ("debugXL", $subr_name, "find index for routine = $routine and current_address = $current_address");
  if (exists ($g_multi_count_function{$routine}))
    {

# TBD: Scan all of the function_info list. Or beter: add index to g_multi_count_function!!

      gp_message ("debugXL", $subr_name, "$routine: occurrences = $g_function_occurrences{$routine}");
      for my $ref (keys @{ $g_map_function_to_index{$routine} })
        {
          $ref_index = $g_map_function_to_index{$routine}[$ref];

          gp_message ("debugXL", $subr_name, "$routine: retrieving duplicate entry at ref_index = $ref_index");
          gp_message ("debugXL", $subr_name, "$routine: function_info[$ref_index]{'alt_name'} = $function_info[$ref_index]{'alt_name'}");

          $addr_offset = $function_info[$ref_index]{"addressobjtext"};
          gp_message ("debugXL", $subr_name, "$routine: addr_offset = $addr_offset");

          $addr_offset =~ s/^@\d+://;
          gp_message ("debugXL", $subr_name, "$routine: addr_offset = $addr_offset");
          if ($addr_offset eq $current_address)
            {
              last;
            }
        }
    }
  else
    {
#------------------------------------------------------------------------------
# There is only a single occurrence and it is straightforward to get the index.
#------------------------------------------------------------------------------
      if (exists ($g_map_function_to_index{$routine}))
        {
          $ref_index = $g_map_function_to_index{$routine}[0];
        }
      else
        {
          my $msg = "index for $routine cannot be determined";
          gp_message ("assertion", $subr_name, $msg);
        }
    }

  gp_message ("debugXL", $subr_name, "routine = $routine current_address = $current_address ref_index = $ref_index");

  return (\$ref_index);

} #-- End of subroutine find_index_in_function_info

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub find_keyword_in_string
{
  my $subr_name = get_my_name ();

  my ($target_string_ref, $target_keyword_ref) = @_;

  my $target_string  = ${ $target_string_ref };
  my $target_keyword = ${ $target_keyword_ref };
  my $foundit = $FALSE;

  my @index_values = ();

    my $ret_val = 0;
    my $offset = 0;
    gp_message ("debugXL", $subr_name, "target_string = $target_string");
    $ret_val = index ($target_string, $target_keyword, $offset);
    gp_message ("debugXL", $subr_name, "ret_val = $ret_val");

    if ($ret_val != -1)
      {
        $foundit = $TRUE;
        while ($ret_val != -1)
          {
             push (@index_values, $ret_val);
             $offset = $ret_val + 1;
             gp_message ("debugXL", $subr_name, "ret_val = $ret_val offset = $offset");
             $ret_val = index ($target_string, $target_keyword, $offset);
          }
        for my $i (keys @index_values)
          {
            gp_message ("debugXL", $subr_name, "index_values[$i] = $index_values[$i]");
          }
      }
    else
      {
        gp_message ("debugXL", $subr_name, "target keyword $target_keyword not found");
      }

  return (\$foundit, \@index_values);

} #-- End of subroutine find_keyword_in_string

#------------------------------------------------------------------------------
# Retrieve the absolute path that was used to execute the command.  This path
# is used to execute gprofng-display-text later on.
#------------------------------------------------------------------------------
sub find_path_to_gp_display_text
{
  my $subr_name = get_my_name ();

  my ($full_command_ref) = @_;

  my $full_command = ${ $full_command_ref };

  my $error_occurred = $TRUE;
  my $return_value;

#------------------------------------------------------------------------------
# Get the path name.
#------------------------------------------------------------------------------
  my ($gp_file_name, $gp_path, $suffix_not_used) = fileparse ($full_command);

  gp_message ("debug", $subr_name, "full_command = $full_command");
  gp_message ("debug", $subr_name, "gp_path  = $gp_path");

  my $gp_display_text_instance = $gp_path . $GP_DISPLAY_TEXT;

#------------------------------------------------------------------------------
# Check if $GP_DISPLAY_TEXT exists, is not empty, and executable.
#------------------------------------------------------------------------------
  if (not -e $gp_display_text_instance)
    {
      $return_value = "file not found";
    }
  else
    {
      if (is_file_empty ($gp_display_text_instance))
        {
          $return_value = "file is empty";
        }
      else
        {
#------------------------------------------------------------------------------
# All is well.  Capture the path.
#------------------------------------------------------------------------------
          $error_occurred = $FALSE;
          $return_value = $gp_path;
        }
    }

  return (\$error_occurred, \$gp_path, \$return_value);

} #-- End of subroutine find_path_to_gp_display_text

#------------------------------------------------------------------------------
# Scan the command line to see if the specified option is present.
#
# Two types of options are supported: options without a value (e.g. --help) or
# those that are set to "on" or "off".
#
# In this phase, we only need to check if a value is valid. If it is, we have
# to enable the corresponding global setting.  If the value is not valid, we
# ignore it, since it will be caught later and a warning message is issued.
#------------------------------------------------------------------------------
sub find_target_option
{
  my $subr_name = get_my_name ();

  my ($command_line_ref, $option_requires_value, $target_option) = @_;

  my @command_line     = @{ $command_line_ref };
  my $option_value     = undef;
  my $found_option     = $FALSE;

  my ($command_line_string) = join (" ", @command_line);

##  if ($command_line_string =~ /\s*($target_option)\s*(on|off)*\s*/)
#------------------------------------------------------------------------------
# This does not make any assumptions on the values we are looking for.
#------------------------------------------------------------------------------
  if ($command_line_string =~ /\s*\-\-($target_option)\s*(\w*)\s*/)
    {
      if (defined ($1))
#------------------------------------------------------------------------------
# We have found the option we are looking for.
#------------------------------------------------------------------------------
        {
          $found_option = $TRUE;
          if ($option_requires_value and defined ($2))
#------------------------------------------------------------------------------
# There is a value and it is passed on to the caller.
#------------------------------------------------------------------------------
            {
              $option_value = $2;
            }
        }
    }

  return ($found_option, $option_value);

} #-- End of subroutine find_target_option

#------------------------------------------------------------------------------
# Find the occurrences of non-space characters in a string and return their
# start and end index values(s).
#------------------------------------------------------------------------------
sub find_words_in_line
{
  my $subr_name = get_my_name ();

  my ($input_line_ref) = @_;

  my $input_line = ${ $input_line_ref };

  my $finished = $TRUE;

  my $space = 0;
  my $space_position = 0;
  my $start_word;
  my $end_word;

  my @word_delimiters = ();

  gp_message ("debugXL", $subr_name, "input_line = $input_line");

    $finished = $FALSE;
    while (not $finished)
      {
        $space = index ($input_line, " ", $space_position);

        my $txt = "string search space_position = $space_position ";
        $txt   .= "space = $space";
        gp_message ("debugXL", $subr_name, $txt);

        if ($space != -1)
          {
            if ($space > $space_position)
              {
                $start_word = $space_position;
                $end_word   = $space - 1;
                $space_position = $space;
                my $keyword = substr ($input_line, $start_word, $end_word - $start_word + 1);
                gp_message ("debugXL", $subr_name, "string search start_word = $start_word end_word = $end_word space_position = $space_position $keyword");
                push (@word_delimiters, [$start_word, $end_word]);
              }
            elsif ( ($space == $space_position) and ($space < length ($input_line) - 1))
              {
                $space          = $space + 1;
                $space_position = $space;
              }
            else
              {
                print "DONE\n";
                $finished = $TRUE;
                gp_message ("debugXL", $subr_name, "completed - finished = $finished");
              }
          }
        else
          {
            $finished = $TRUE;
            $start_word = $space_position;
            $end_word = length ($input_line) - 1;
            my $keyword = substr ($input_line, $start_word, $end_word - $start_word + 1);
            push (@word_delimiters, [$start_word, $end_word]);
            if ($keyword =~ /\s+/)
              {
                my $txt = "end search spaces only";
                gp_message ("debugXL", $subr_name, $txt);
              }
            else
              {
                my $txt  = "end search start_word = $start_word ";
                $txt    .= "end_word = $end_word ";
                $txt    .= "space_position = $space_position -->$keyword<--";
                gp_message ("debugXL", $subr_name, $txt);
              }
          }

       }

  for my $i (keys @word_delimiters)
    {
      gp_message ("debugXL", $subr_name, "i = $i $word_delimiters[$i][0] $word_delimiters[$i][1]");
    }

  return (\@word_delimiters);

} #-- End of subroutine find_words_in_line

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub function_info
{
  my $subr_name = get_my_name ();

  my ($outputdir, $FUNC_FILE, $metric, $LINUX_vDSO_ref) = @_;

  my %LINUX_vDSO = %{ $LINUX_vDSO_ref };

  my $index_val;
  my $address_decimal;
  my $full_address_field;

  my $FUNC_FILE_NO_PC;
  my $off_with_the_PC;

  my $blanks;
  my $lblanks;
  my $lvdso_key;
  my $line_regex;

  my %functions_per_metric_indexes = ();
  my %functions_per_metric_first_index = ();
  my @order;

  my ($line,$line_n,$value);
  my ($df_flag,$n,$u);
  my ($metric_value,$PC_Address,$routine);
  my ($is_calls,$metric_ok,$name_regex,$pc_len);
  my ($segment,$offset,$offy,$spaces,$rest,$not_printed,$vdso_key);

#------------------------------------------------------------------------------
# If the directory name does not end with a "/", add it.
#------------------------------------------------------------------------------
  my $length_of_string = length ($outputdir);

  if (rindex ($outputdir, "/") != $length_of_string-1)
    {
      $outputdir .= "/";
    }

  gp_message ("debug", $subr_name, "on input FUNC_FILE = $FUNC_FILE metric = $metric");

  $is_calls        = $FALSE;
  $metric_ok       = $TRUE;
  $off_with_the_PC = rindex ($FUNC_FILE, "-PC");
  $FUNC_FILE_NO_PC = substr ($FUNC_FILE, 0, $off_with_the_PC);

  if ($FUNC_FILE_NO_PC eq $outputdir."calls.sort.func")
    {
      $FUNC_FILE_NO_PC = $outputdir."calls";
      $is_calls        = $TRUE;
      $metric_ok       = $FALSE;
    }
  elsif ($FUNC_FILE_NO_PC eq $outputdir."calltree.sort.func")
    {
      $FUNC_FILE_NO_PC = $outputdir."calltree";
      $metric_ok       = $FALSE;
    }
  elsif ($FUNC_FILE_NO_PC eq $outputdir."functions.sort.func")
    {
      $FUNC_FILE_NO_PC = $outputdir."functions.func";
      $metric_ok       = $FALSE;
    }
  gp_message ("debugM", $subr_name, "set FUNC_FILE_NO_PC = $FUNC_FILE_NO_PC");

  open (FUNC_FILE, "<", $FUNC_FILE)
    or die ("Not able to open file $FUNC_FILE for reading - '$!'");
  gp_message ("debug", $subr_name, "opened file FUNC_FILE = $FUNC_FILE for reading");

  open (FUNC_FILE_NO_PC, ">", $FUNC_FILE_NO_PC)
    or die ("Not able to open file $FUNC_FILE_NO_PC for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file FUNC_FILE_NO_PC = $FUNC_FILE_NO_PC for writing");

  open (FUNC_FILE_REGEXP, "<", "$FUNC_FILE.name-regex")
    or die ("Not able to open file $FUNC_FILE.name-regex for reading - '$!'");
  gp_message ("debug", $subr_name, "opened file FUNC_FILE_REGEXP = $FUNC_FILE.name-regex for reading");

  $name_regex = <FUNC_FILE_REGEXP>;
  chomp ($name_regex);
  close (FUNC_FILE_REGEXP);

  gp_message ("debugXL", $subr_name, "name_regex = $name_regex");

  $n = 0;
  $u = 0;
  $pc_len = 0;

#------------------------------------------------------------------------------
# Note that the double \\ is needed here.  The regex used will not have these.
#------------------------------------------------------------------------------
  if ($is_calls)
    {
#------------------------------------------------------------------------------
# TBD
# I do not see the "*" in my test output, but no harm to leave the code in.
#
# er_print * before PC for calls ! 101315
#------------------------------------------------------------------------------
      $line_regex = "^(\\s*)(\\**)(\\S+)(:)(\\S+)(\\s+)(.*)";
    }
  else
    {
      $line_regex = "^(\\s*)(\\S+)(:)(\\S+)(\\s+)(.*)";
    }
  gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." line_regex->$line_regex<-");
  gp_message ("debugXL", $subr_name, "read FUNC_FILE = $FUNC_FILE");

  $line_n = 0;
  $index_val = 0;
  while (<FUNC_FILE>)
    {
      $line = $_;
      chomp ($line);
      $line =~ s/ --  no functions found//;

      gp_message ("debug", $subr_name, "FUNC_FILE: input line = $line");

      $line_n++;
      if ($line =~ /$line_regex/) # field 2|3 needs to be \S in case of -ve sign
        {
#------------------------------------------------------------------------------
# A typical target line looks like this:
# 11:0x001492e0  6976.900   <additional_timings> _lwp_start
#------------------------------------------------------------------------------
          gp_message ("debugXL", $subr_name, "select = $line");
          if ($is_calls)
            {
              $segment = $3;
              $offset  = $5;
              $spaces  = $6;
              $rest    = $7;
              $PC_Address = $segment.$4.$offset; # PC Addr.
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$3 = $3");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$5 = $5");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$6 = $6");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$7 = $7");
            }
          else
            {
              $segment = $2;
              $offset  = $4;
              $spaces  = $5;
              $rest    = $6;
              $PC_Address = $segment.$3.$offset; # PC Addr.
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$2 = $2");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$4 = $4");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$5 = $5");
              gp_message ("debugXL", $subr_name, "is_calls = ".(($is_calls) ? "TRUE" : "FALSE")." \$6 = $6");
            }
          if ($segment == -1)
            {
#------------------------------------------------------------------------------
# presume vDSO field overflow - er_print used an inadequate format
# or the fsummary (MASTER) had the wrong format for -1?
# rats - get ahead of ourselves - should not be a field abuttal so
#------------------------------------------------------------------------------
              if ($line =~ /$name_regex/)
                {
                  if ($metric_ok)
                    {
                      $metric_value = $1; # whatever
                      $routine = $2;
                    }
                  else
                    {
                      $routine = $1;
                    }
                  if ($is_calls)
                    {
                      if (substr ($routine,0,1) eq "*")
                        {
                          $routine = substr ($routine,1);
                        }
                    }
                  for $vdso_key (keys %LINUX_vDSO)
                    {
                      if ($routine eq $LINUX_vDSO{$vdso_key})
                        {
#------------------------------------------------------------------------------
# presume no duplicates - at least can check offset
#------------------------------------------------------------------------------
                          if ($vdso_key =~ /(\d+):(\S+)/)
#------------------------------------------------------------------------------
# no -ve segments allowed and not expected
#------------------------------------------------------------------------------
                            {
                              if ($2 eq $offset)
                                {
#------------------------------------------------------------------------------
# the real segment
#------------------------------------------------------------------------------
                                  $segment = $1;
                                  gp_message ("debugXL", $subr_name, "rescued segment for $PC_Address($routine)->$segment:$offset $FUNC_FILE");
                                  $PC_Address = $segment.":".$offset; # PC Addr.
                                  gp_message ("debugXL", $subr_name, "vdso line ->$line");
                                  $line = $PC_Address.(' ' x (length ($spaces)-2)).$rest;
                                  gp_message ("debugXL", $subr_name, "becomes   ->$line");
                                  last;
                                }
                            }
                        }
                    }
                }
              else
                {
                  gp_message ("debug", $subr_name, "name_regex failure for file $FUNC_FILE");
                }
            }

#------------------------------------------------------------------------------
# a rotten exception for Linux vDSO
# With a BIG "PC Address" like 32767:0x841fecd0, the functions.sort.func_PC file
# can have lines like
#->32767:0x841fecd0161.553   527182898954  131.936    100003     __vdso_gettimeofday<-
#->32767:0x153ff810 42.460   0                   0   __vdso_gettimeofday<-
#->-1:0xff600000   99.040   0                   0   [vsyscall]<-
#  (Real PC Address: 4294967295:0xff600000)
#-> 4294967295:0xff600000   99.040   0                   0   [vsyscall]<-
#-> 9:0x00000020   49.310   0                   0   <static>@0x7fff153ff600 ([vdso])<-
# Rats!
# $LINUX_vDSO{substr($order[$i]{"addressobjtext"},1)} = $order[$i]{"routine"};
#------------------------------------------------------------------------------

          $not_printed = $TRUE;
          for $vdso_key (keys %LINUX_vDSO)
            {
              if ($line =~ /^(\s*)($vdso_key)(.*)$/)
                {
                  $blanks = 1;
                  $rest   = 3;
                  $lblanks = length ($blanks);
                  $lvdso_key = length ($vdso_key);
                  $PC_Address = $vdso_key; # PC Addr.
                  $offy = ($lblanks+$lvdso_key < $pc_len) ? $pc_len : $lblanks+$lvdso_key;
                  gp_message ("debugXL", $subr_name, "offy = $offy for ->$line<-");
                  if ($pc_len)
                    {
                      print FUNC_FILE_NO_PC substr ($line,$offy)."\n";
                      $not_printed = $FALSE;
                    }
                  else
                    {
                      die ("sod1a");
                    }
                  gp_message ("debugXL", $subr_name, "vdso line ->$line");
                  if (substr ($line,$lblanks+$lvdso_key,1) eq " ")
                    {
#------------------------------------------------------------------------------
# O.K. no field abuttal
#------------------------------------------------------------------------------
                      gp_message ("debugXL", $subr_name, "vdso no field abuttal line ->$line");
                    }
                  else
                    {
                      gp_message ("debugXL", $subr_name, "vdso field abuttal line ->$line");
                      $line = $blanks.$vdso_key." ".$rest;
                    }
                  gp_message ("debugXL", $subr_name, "becomes   ->$line");
                  last;
                }
            }
          if ($not_printed)
            {
              if ($pc_len)
                {
                  print FUNC_FILE_NO_PC substr ($line,$pc_len)."\n";
                }
              else
                {
                  die ("sod1b");
                }
              $not_printed = $FALSE;
            }
        }
      else
        {
          if (!$pc_len)
            {
              if ($line =~ /(^\s*PC Addr.\s+)(\S+)/)
                {
                  $pc_len = length ($1); # say 15
                  print FUNC_FILE_NO_PC substr ($line,$pc_len)."\n";
                }
              else
                {
                  print FUNC_FILE_NO_PC "$line\n";
                }
            }
          else
            {
              if ($pc_len)
                {
                  my $strlen = length ($line);
                  if ($strlen > 0 )
                    {
                      print FUNC_FILE_NO_PC substr ($line,$pc_len)."\n";
                    }
                  else
                    {
                      print FUNC_FILE_NO_PC "\n";
                    }
                }
              else
                {
                  die ("sod2");
                }
            }
          next;
        }
      $routine = "";
      if ($line =~ /$name_regex/)
        {
          if ($metric_ok)
            {
              $metric_value = $1; # whatever
              $routine = $2;
            }
          else
            {
              $routine = $1;
            }
        }

      if ($is_calls)
        {
          if (substr ($routine,0,1) eq "*")
            {
              $routine = substr ($routine,1);
            }
        }
      if (length ($routine))
        {
          $order[$index_val]{"routine"} = $routine;
          if ($metric_ok)
            {
              $order[$index_val]{"metric_value"} = $metric_value;
            }
          $order[$index_val]{"PC Address"} = $PC_Address;
          $df_flag = 0;
          if (not exists ($functions_per_metric_indexes{$routine}))
            {
              $functions_per_metric_indexes{$routine} = [$index_val];
            }
          else
            {
              push (@{$functions_per_metric_indexes{$routine}},$index_val); # add $RI to list
            }
          gp_message ("debugXL", $subr_name, "updated functions_per_metric_indexes $routine [$index_val] line = $line");
          if ($PC_Address =~ /\s*(\S+):(\S+)/)
            {
              my ($segment,$offset);
              $segment = $1;
              $offset = $2;
              $address_decimal = bigint::hex ($offset); # decimal
##              $full_address_field = '@'.$segment.":".$offset; # e.g. @2:0x0003f280
              $full_address_field = $segment.":".$offset; # e.g. @2:0x0003f280
              $order[$index_val]{"addressobj"} = $address_decimal;
              $order[$index_val]{"addressobjtext"} = $full_address_field;
            }
#------------------------------------------------------------------------------
# Check uniqueness
#------------------------------------------------------------------------------
          if (not exists ($functions_per_metric_first_index{$routine}{$PC_Address}))
            {
              $functions_per_metric_first_index{$routine}{$PC_Address} = $index_val;
              $u++; #$RI
            }
          else
            {
              if (!($metric eq "calls" || $metric eq "calltree"))
                {
                  gp_message ("debug", $subr_name, "file $FUNC_FILE: function $routine already has a PC Address");
                }
            }

          $index_val++;
          gp_message ("debugXL", $subr_name, "updated index_val = $index_val");
          $n++;
          next;
        }
      else
        {
          if ($n && length ($line))
            {
              my $msg = "unexpected line format in functions file $FUNC_FILE line->$line<-";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
    }
  close (FUNC_FILE);
  close (FUNC_FILE_NO_PC);

  for my $i (sort keys %functions_per_metric_indexes)
    {
      my $values = "";
      for my $fields (sort keys @{ $functions_per_metric_indexes{$i} })
        {
           $values .= "$functions_per_metric_indexes{$i}[$fields] ";
        }
      gp_message ("debugXL", $subr_name, "on return: functions_per_metric_indexes{$i} = $values");
    }

  return (\@order, \%functions_per_metric_first_index, \%functions_per_metric_indexes);

} #-- End of subroutine function_info

#------------------------------------------------------------------------------
# Generate a html header.
#------------------------------------------------------------------------------
sub generate_a_header
{
  my $subr_name = get_my_name ();

  my ($page_text_ref, $size_text_ref, $position_text_ref) = @_;

  my $page_text     = ${ $page_text_ref };
  my $size_text     = ${ $size_text_ref };
  my $position_text = ${ $position_text_ref };
  my $html_header;

  $html_header  = "<div class=\"" . $position_text . "\">\n";
  $html_header .= "<". $size_text . ">\n";
  $html_header .= $page_text . "\n";
  $html_header .= "</". $size_text . ">\n";
  $html_header .= "</div>";

  gp_message ("debugXL", $subr_name, "on exit page_title = $html_header");

  return (\$html_header);

} #-- End of subroutine generate_a_header

#------------------------------------------------------------------------------
# Generate the caller-callee information.
#------------------------------------------------------------------------------
sub generate_caller_callee
{
  my $subr_name = get_my_name ();

  my ($number_of_metrics_ref, $function_info_ref, $function_view_structure_ref,
      $function_address_info_ref, $addressobjtextm_ref,
      $input_string_ref) = @_;

  my $number_of_metrics       = ${ $number_of_metrics_ref };
  my @function_info           = @{ $function_info_ref };
  my %function_view_structure = %{ $function_view_structure_ref };
  my %function_address_info   = %{ $function_address_info_ref };
  my %addressobjtextm         = %{ $addressobjtextm_ref };
  my $input_string            = ${ $input_string_ref };

  my @caller_callee_data = ();
  my $caller_callee_data_ref;
  my $outfile;
  my $input_line;

  my $fullname;
  my $separator = "cuthere";

  my @address_field = ();
  my @fields = ();
  my @function_names = ();
  my @marker = ();
  my @metric_values = ();
  my @word_index_values = ();
  my @header_lines = ();

  my $all_metrics;
  my $elements_in_name;
  my $full_hex_address;
  my $hex_address;
  my $msg;

  my $remainder2;

  my $file_title;
  my $page_title;
  my $size_text;
  my $position_text;
  my @html_metric_sort_header = ();
  my $html_header;
  my $html_title_header;
  my $html_home;
  my $html_acknowledgement;
  my $html_end;
  my $html_line;

  my $marker_target_function;
  my $max_metrics_length = 0;
  my $metrics_length;
  my $modified_line;
  my $name_regex;
  my $no_of_fields;
  my $routine;
  my $routine_length;
  my $string_length;
  my $top_header;
  my $total_header_lines;
  my $word_index_values_ref;
  my $infile;

  my $outputdir               = append_forward_slash ($input_string);
  my $LANG                    = $g_locale_settings{"LANG"};
  my $decimal_separator       = $g_locale_settings{"decimal_separator"};

  gp_message ("debug", $subr_name, "decimal_separator = $decimal_separator");
  gp_message ("debug", $subr_name, "outputdir = $outputdir");

  $infile  = $outputdir . "caller-callee-PC2";
  $outfile = $outputdir . $g_html_base_file_name{"caller_callee"} . ".html";

  gp_message ("debug", $subr_name, "infile = $infile outfile = $outfile");

  open (CALLER_CALLEE_IN, "<", $infile)
    or die ("unable to open caller file $infile for reading - '$!'");
  gp_message ("debug", $subr_name, "opened file $infile for reading");

  open (CALLER_CALLEE_OUT, ">", $outfile)
    or die ("unable to open $outfile for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile for writing");

  $msg = "building caller-callee file " . $outfile;
  gp_message ("debug", $subr_name, $msg);
  gp_message ("verbose", $subr_name, $msg);

#------------------------------------------------------------------------------
# Generate some of the structures used in the HTML output.
#------------------------------------------------------------------------------
  $file_title  = "Caller-callee overview";
  $html_header = ${ create_html_header (\$file_title) };
  $html_home   = ${ generate_home_link ("right") };

  $page_title    = "Caller Callee View";
  $size_text     = "h2";
  $position_text = "center";
  $html_title_header = ${ generate_a_header (\$page_title,
					     \$size_text,
					     \$position_text) };

#------------------------------------------------------------------------------
# Read all of the file into an array with the name caller_callee_data.
#------------------------------------------------------------------------------
  chomp (@caller_callee_data = <CALLER_CALLEE_IN>);

#------------------------------------------------------------------------------
# Remove a legacy redundant string, if any.
#------------------------------------------------------------------------------
  @caller_callee_data = @{ remove_redundant_string (\@caller_callee_data)};

#------------------------------------------------------------------------------
# Typical structure of the input file:
#
# Current metrics: address:name:e.totalcpu:e.cycles:e+insts:e+llm
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Functions sorted by metric: Exclusive Total CPU Time
# Callers and callees sorted by metric: Attributed Total CPU Time
#
# PC Addr.       Name              Attr.     Attr. CPU  Attr.         Attr.
#                                  Total     Cycles     Instructions  Last-Level
#                                  CPU sec.   sec.      Executed      Cache Misses
# 1:0x00000000  *<Total>           3.502     4.005      15396819700   24024250
# 7:0x00008070   start_thread      3.342     3.865      14500538981   23824045
# 6:0x000233a0   __libc_start_main 0.160     0.140        896280719     200205
#
# PC Addr.       Name              Attr.     Attr. CPU  Attr.         Attr.
#                                  Total     Cycles     Instructions  Last-Level
#                                  CPU sec.   sec.      Executed      Cache Misses
# 2:0x000021f9   driver_mxv        3.342     3.865      14500538981   23824045
# 2:0x000021ae  *mxv_core          3.342     3.865      14500538981   23824045
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Scan the input file.  The first lines are assumed to be part of the header,
# so we store those. The diagnostic lines that echo some settings are also
# stored, but currently not used.
#------------------------------------------------------------------------------
  my $scan_header = $FALSE;
  my $scan_caller_callee_data = $FALSE;
  my $data_function_block = "";
  my @function_blocks = ();
  my $first = $TRUE;
  my @html_caller_callee = ();
  my @top_level_header = ();

#------------------------------------------------------------------------------
# The regexes.
#------------------------------------------------------------------------------
  my $empty_line_regex       = '^\s*$';
  my $line_of_interest_regex = '\s*(\d+:0x[a-fA-F0-9]+)\s+(\**)(.*)';
  my $get_hex_address_regex  = '(\d+):0x(\S+)';
  my $get_metric_field_regex = ')\s+([\s\d' . $decimal_separator . ']*)';
  my $header_name_regex      = '(.*\.)(\s+)(Name)\s+(.*)';
  my $sorted_by_regex        = 'sorted by metric:';
  my $current_regex          = '^Current';
  my $get_addr_offset_regex  = '^@\d+:';

#------------------------------------------------------------------------------
# Get the length of the first metric field across all lines.  This value is
# used to pad the first metric with spaces and get the alignment right.
#
# Scan the input data and find the line(s) with metric values.  A complication
# is that a function name may consists of more than one field.
#
# Note.  This part could be used to parse the other elements of the input file,
# but that makes the loop very complicated.   Instead, we re-scan the data
# below and process each block separately.
#
# Since this data is all in memory and relatively small, the performance should
# not suffer much, but it does improve the readability of the code.
#------------------------------------------------------------------------------
  $g_max_length_first_metric = 0;

  my @hex_addresses = ();
  my @metrics_array = ();
  my @length_first_metric = ();
  my @special_marker = ();
  my @the_function_name = ();
  my @the_metrics = ();

  my $find_hex_address_regex = '\s*(\d+:0x[a-fA-F0-9]+)\s+(.*)';
  my $find_metric_values_regex  = '\)\s+\[.*\]\s+(\d+';
     $find_metric_values_regex .= '[\.\d\ ]*)|\)\s+(\d+[\.\d\ ]*)';
  my $find_marker_regex = '(^\*).*';

  my @html_block_prologue;
  my @html_code_function_block;
  my $marker;
  my $list_with_metrics;
  my $reduced_line;

  $msg  = "loop over the caller-callee data - number of lines = ";
  $msg .= ($#caller_callee_data + 1);
  gp_message ("debugXL", $subr_name, $msg);

  for (my $line = 0; $line <= $#caller_callee_data; $line++)
    {
      $input_line = $caller_callee_data[$line];
      $reduced_line = $input_line;

      $msg = "line = " . $line . " input_line = " . $input_line;
      gp_message ("debugXL", $subr_name, $msg);

      if ($input_line =~ /$find_hex_address_regex/)
#------------------------------------------------------------------------------
# This is an input line of interest.
#------------------------------------------------------------------------------
        {
          my ($hex_address_ref, $marker_ref, $reduced_line_ref, 
              $list_with_metrics_ref) =
                                       split_function_data_line (\$input_line);

          $hex_address       = ${ $hex_address_ref };
          $marker            = ${ $marker_ref };
          $reduced_line      = ${ $reduced_line_ref };
          $list_with_metrics = ${ $list_with_metrics_ref };

          $msg = "RESULT full_hex_address = " . $hex_address;
          $msg .= " -- metric values = " . $list_with_metrics;
          $msg .= " -- marker = " . $marker;
          $msg .= " -- function name = " . $reduced_line;
          gp_message ("debugXL", $subr_name, $msg);
 
#------------------------------------------------------------------------------
# Store the address and marker.
#------------------------------------------------------------------------------
          push (@the_function_name, $reduced_line);
          push (@hex_addresses, $hex_address);
          if ($marker eq "*")
            {
              push (@special_marker, "*");
            }
          else
            {
              push (@special_marker, "X");
            }
#------------------------------------------------------------------------------
# Processing of the metrics.
#------------------------------------------------------------------------------
          @metrics_array = split (" ", $list_with_metrics);

#------------------------------------------------------------------------------
# If the first metric is 0. (or 0, depending on the locale), the calculation
# of the length needs to be adjusted, because 0. is really 0.000.
#
# While we could easily add 3 to the length, we assign a symbolic value to the
# first metric (ZZZ) and then compute the length.  This makes things clearer.
# I hope ;-)
#------------------------------------------------------------------------------
          my $first_metric = $metrics_array[0];
          $msg = "first metric found = " . $first_metric;
          gp_message ("debugXL", $subr_name, $msg);
          if ($first_metric =~ /^0$decimal_separator$/)
            {
              $first_metric = "0.ZZZ";
              $msg = "fixed up $first_metric";
              gp_message ("debugXL", $subr_name, $msg);
            }
              $g_max_length_first_metric = max ($g_max_length_first_metric, 
						length ($first_metric));

              $msg = "first_metric = $first_metric " .
                     "g_max_length_first_metric = $g_max_length_first_metric";
              gp_message ("debugXL", $subr_name, $msg);
              push (@length_first_metric, length ($first_metric));
              push (@the_metrics, $list_with_metrics);
        }
    }

  $msg = "the following function names have been found";
  gp_message ("debugM", $subr_name, $msg);
  for my $i (0 .. $#the_function_name)
    {
      $msg = "the_function_name{" . $i . "] = " . $the_function_name[$i];
      gp_message ("debugM", $subr_name, $msg);
    }

  $msg = "final: g_max_length_first_metric = " . $g_max_length_first_metric;
  gp_message ("debugM", $subr_name, $msg);
  $msg = "\$#hex_addresses = " . $#hex_addresses;
  gp_message ("debugM", $subr_name, $msg);

#------------------------------------------------------------------------------
# Main loop over the input data.
#------------------------------------------------------------------------------
  my $index_start = 0;  # 1
  my $index_end   = -1;  # 0
  for (my $line = 0; $line <= $#caller_callee_data; $line++)
    {
      $input_line = $caller_callee_data[$line];

      if ($input_line =~ /$header_name_regex/)
        {
          $scan_header = $TRUE;
          $msg  = "line = " . $line . " encountered start of the header";
          $msg .= " scan_header = " . $scan_header . " first = " . $first;
          gp_message ("debugXL", $subr_name, $msg);
        }
      elsif (($input_line =~ /$sorted_by_regex/) or
             ($input_line =~ /$current_regex/))
        {
          $msg =  "line = " . $line . " captured top level header: " .
                     "input_line = " . $input_line;
          gp_message ("debugXL", $subr_name, $msg);

          push (@top_level_header, $input_line);
        }
      elsif ($input_line =~ /$line_of_interest_regex/)
        {
          $index_end++;
          $scan_header             = $FALSE;
          $scan_caller_callee_data = $TRUE;
          $data_function_block    .= $separator . $input_line;

          $msg = "line = $line updated index_end   = $index_end";
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "line = $line input_line          = " . $input_line;
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "line = $line data_function_block = " . $data_function_block;
          gp_message ("debugXL", $subr_name, $msg);
        }
      elsif (($input_line =~ /$empty_line_regex/) and
             ($scan_caller_callee_data))
        {
#------------------------------------------------------------------------------
# An empty line is interpreted as the end of the current block and we process
# this, including the generation of the html code for this block.
#------------------------------------------------------------------------------
          $first = $FALSE;
          $scan_caller_callee_data = $FALSE;

          $msg = "new block";
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "line = " . $line . " index_start = " . $index_start;
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "line = " . $line . " index_end   = " . $index_end;
          gp_message ("debugXL", $subr_name, $msg);

          $msg  = "line = " . $line . " data_function_block = ";
          $msg .= $data_function_block;
          gp_message ("debugXL", $subr_name, $msg);

          push (@function_blocks, $data_function_block);

##          $msg  = "    generating the html blocks (";
##          $msg .= $index_start . " - " . $index_end .")";
##          gp_message ("verbose", $subr_name, $msg);

          my ($html_block_prologue_ref, $html_code_function_block_ref) =
					generate_html_function_blocks (
						\$index_start,
						\$index_end,
						\@hex_addresses,
						\@the_metrics,
						\@length_first_metric,
						\@special_marker,
						\@the_function_name,
						\$separator,
						$number_of_metrics_ref,
						\$data_function_block,
						$function_info_ref,
						$function_view_structure_ref);

          @html_block_prologue      = @{ $html_block_prologue_ref };
          @html_code_function_block = @{ $html_code_function_block_ref };

          for my $lines (0 .. $#html_code_function_block)
            {
              $msg = "final html_code_function_block[" . $lines . "] = " .
                        $html_code_function_block[$lines];
              gp_message ("debugXL", $subr_name, $msg);
            }

          $data_function_block = "";

          push (@html_caller_callee, @html_block_prologue);
          push (@html_caller_callee, @header_lines);
          push (@html_caller_callee, @html_code_function_block);

          $index_start = $index_end + 1;
          $index_end   = $index_start - 1;
          $msg = "line = " . $line . " reset index_start = " . $index_start;
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "line = " . $line . " reset index_end   = " . $index_end;
          gp_message ("debugXL", $subr_name, $msg);
        }

#------------------------------------------------------------------------------
# Only capture the first header.  They are all identical.
#------------------------------------------------------------------------------
      if ($scan_header and $first)
        {
          if (defined ($4))
            {
#------------------------------------------------------------------------------
# This group is only defined for the first line of the header.
#------------------------------------------------------------------------------
              gp_message ("debugXL", $subr_name, "header1 = $4");
              gp_message ("debugXL", $subr_name, "extra   = $3 spaces=x$2x");
              my $newline = "<b>" . $4 . "</b>";
              push (@header_lines, $newline);
            }
          elsif ($input_line =~ /\s*(.*)/)
            {
#------------------------------------------------------------------------------
# Capture the subsequent header lines.
#------------------------------------------------------------------------------
              gp_message ("debugXL", $subr_name, "headern = $1");
              my $newline = "<b>" . $1 . "</b>";
              push (@header_lines, $newline);
            }
        }

    }

  for my $i (0 .. $#header_lines)
    {
      gp_message ("debugXL", $subr_name, "header_lines[$i] = $header_lines[$i]");
    }
  for my $i (0 .. $#function_blocks)
    {
      gp_message ("debugXL", $subr_name, "function_blocks[$i] = $function_blocks[$i]");
    }

  my $number_of_blocks = $#function_blocks + 1;
  gp_message ("debugXL", $subr_name, "There are " . $number_of_blocks . " function blocks:");

  for my $i (0 .. $#function_blocks)
    {
#------------------------------------------------------------------------------
# The split produces an empty first field and is why we skip the first field.
#------------------------------------------------------------------------------
##      my @entries = split ("cuthere", $function_blocks[$i]);
      my @entries = split ($separator, $function_blocks[$i]);
      for my $k (1 .. $#entries)
        {
          my $msg = "entries[" . $k . "] = ". $entries[$k];
          gp_message ("debugXL", $subr_name, $k . $msg);
        }
    }

#------------------------------------------------------------------------------
# Parse and process the individual function blocks.
#------------------------------------------------------------------------------
  $msg  = "Parse and process function blocks - total blocks = ";
  $msg .= $#function_blocks + 1;
  gp_message ("verbose", $subr_name, $msg);

  for my $i (0 .. $#function_blocks)
    {
      $msg = "process function block " . $i;
      gp_message ("debugXL", $subr_name, $msg);

      $msg = "function_blocks[" . $i . "] = ". $function_blocks[$i];
      gp_message ("debugXL", $subr_name, $msg);
#------------------------------------------------------------------------------
# This split produces an empty first field.  This is why we skip this in the
# loop below.
#------------------------------------------------------------------------------
      my @entries = split ($separator, $function_blocks[$i]);

#------------------------------------------------------------------------------
# An example of the content of array @entries:
# <empty line>
# 6:0x0003ad20   drand48           0.100     0.084        768240570          0
# 6:0x0003af50  *erand48_r         0.080     0.084        768240570          0
# 6:0x0003b160   __drand48_iterate 0.020     0.                   0          0
#------------------------------------------------------------------------------
      for my $k (1 .. $#entries)
        {
          my $input_line = $entries[$k];

          $msg = "input_line = entries[" . $k . "] = ". $entries[$k];
          gp_message ("debugXL", $subr_name, $msg);

          my ($hex_address_ref, $marker_ref, $reduced_line_ref,
              $list_with_metrics_ref) =
                                       split_function_data_line (\$input_line);

          $full_hex_address       = ${ $hex_address_ref };
          $marker_target_function = ${ $marker_ref };
          $routine                = ${ $reduced_line_ref };
          $all_metrics            = ${ $list_with_metrics_ref };

          $msg = "RESULT full_hex_address = " . $full_hex_address;
          $msg .= " -- metric values = " . $all_metrics;
          $msg .= " -- marker = " . $marker_target_function;
          $msg .= " -- function name = " . $routine;
          gp_message ("debugXL", $subr_name, $msg);

          $metrics_length = length ($all_metrics);
          $max_metrics_length = max ($max_metrics_length, $metrics_length);

          if ($full_hex_address =~ /(\d+):0x(\S+)/)
            {
              $hex_address = "0x" . $2;
            }
          push (@marker, $marker_target_function);

          push (@address_field, $hex_address);
          push (@address_field, $full_hex_address);
          $msg  = "pushed " . $full_hex_address;
          $msg .= " to array address_field";
          gp_message ("debugXL", $subr_name, $msg);

          $modified_line = $all_metrics . " " . $routine;
          gp_message ("debugXL", $subr_name, "xxxxxxx = $modified_line");

          push (@metric_values, $all_metrics);
          $msg = "pushed " . $all_metrics . " to array metric_values";
          gp_message ("debugXL", $subr_name, $msg);

          push (@function_names, $routine);
          $msg = "pushed " . $routine . " to array function_names";
          gp_message ("debugXL", $subr_name, $msg);
        }

      $total_header_lines = $#header_lines + 1;
      $msg = "total_header_lines = " . $total_header_lines;
      gp_message ("debugXL", $subr_name, $msg);

      gp_message ("debugXL", $subr_name, "Final output");
      for my $i (keys @header_lines)
        {
          gp_message ("debugXL", $subr_name, "$header_lines[$i]");
        }
      for my $i (0 .. $#function_names)
        {
          $msg  = $metric_values[$i] . " " . $marker[$i]; 
          $msg .= $function_names[$i] . " (" . $address_field[$i] . ")";
          gp_message ("debugXL", $subr_name, $msg);
        }
#------------------------------------------------------------------------------
# Check if this function has multiple occurrences.
# TBD: Replace by the function call for this.
#------------------------------------------------------------------------------
      $msg  = "check for multiple occurrences - function_names = ";
      $msg .= ($#function_names + 1);
      gp_message ("debugXL", $subr_name, $msg);

      for my $i (0 .. $#function_names)
        {
          my $current_address = $address_field[$i];
          my $found_a_match;
          my $ref_index;
          my $alt_name;
          my $addr_offset;
 
          $routine = $function_names[$i];
          $alt_name = $routine;
          gp_message ("debugXL", $subr_name, "checking for routine = $routine");
          if (exists ($g_multi_count_function{$routine}))
            {
#------------------------------------------------------------------------------
# TBD: Scan all of the function_info list. Or beter: add index to
# g_multi_count_function.
#------------------------------------------------------------------------------

              $found_a_match = $FALSE;

              $msg  = $routine . ": occurrences = ";
              $msg .= $g_function_occurrences{$routine};
              gp_message ("debugXL", $subr_name, $msg);

              for my $ref (keys @{ $g_map_function_to_index{$routine} })
                {
                  $ref_index = $g_map_function_to_index{$routine}[$ref];

                  $msg  = $routine . ": retrieving duplicate entry at ";
                  $msg .= "ref_index = " . $ref_index;
                  gp_message ("debugXL", $subr_name, $msg);
                  $msg  = $routine . ": function_info[" . $ref_index;
                  $msg .= "]{alt_name} = ";
                  $msg .= $function_info[$ref_index]{'alt_name'};
                  gp_message ("debugXL", $subr_name, $msg);

                  $addr_offset = $function_info[$ref_index]{"addressobjtext"};
                  $msg = $routine . ": addr_offset = " . $addr_offset;
                  gp_message ("debugXL", $subr_name, $msg);

                  $addr_offset =~ s/$get_addr_offset_regex//;
                  $msg = $routine . ": addr_offset = " . $addr_offset;
                  gp_message ("debugXL", $subr_name, $msg);

                  if ($addr_offset eq $current_address)
                    {
                      $found_a_match = $TRUE;
                      last;
                    }
                }
              $msg  = $function_info[$ref_index]{'alt_name'};
              $msg .= " is the actual function for i = " . $i . " ";
              $msg .= $found_a_match;
              gp_message ("debugXL", $subr_name, $msg);

              $alt_name = $function_info[$ref_index]{'alt_name'};
            }
          gp_message ("debugXL", $subr_name, "alt_name = $alt_name");
        }
      $msg = "completed the check for multiple occurrences";
      gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Figure out the column width.  Since the columns in the header may include
# spaces, we use the first line with metrics for this.
#------------------------------------------------------------------------------
      my $top_header = $metric_values[0];
      my $word_index_values_ref = find_words_in_line (\$top_header);
      my @word_index_values = @{ $word_index_values_ref };

# $i = 0 0 4
# $i = 1 10 14
# $i = 2 21 31
# $i = 3 35 42
      for my $i (keys @word_index_values)
        {
          $msg  = "i = " . $i . " " . $word_index_values[$i][0] . " ";
          $msg .= $word_index_values[$i][1];
          gp_message ("debugXL", $subr_name, $msg);
        }

#------------------------------------------------------------------------------
# Empty the buffers before processing the next block with data.
#------------------------------------------------------------------------------
      @function_names = ();
      @metric_values = ();
      @address_field = ();
      @marker = ();
 
      $msg  = "erased contents of arrays function_names, metric_values, ";
      $msg .= "address_field, and marker";
      gp_message ("debugXL", $subr_name, $msg);

    }

  push (@html_metric_sort_header, "<i>");
  for my $i (0 .. $#top_level_header)
    {
      $html_line = $top_level_header[$i] . "<br>";
      push (@html_metric_sort_header, $html_line);
    }
  push (@html_metric_sort_header, "</i>");

  print CALLER_CALLEE_OUT $html_header;
  print CALLER_CALLEE_OUT $html_home;
  print CALLER_CALLEE_OUT $html_title_header;
  print CALLER_CALLEE_OUT "$_" for @g_html_experiment_stats;
##  print CALLER_CALLEE_OUT "<br>\n";
##  print CALLER_CALLEE_OUT "$_\n" for @html_metric_sort_header;
  print CALLER_CALLEE_OUT "<pre>\n";
  print CALLER_CALLEE_OUT "$_\n" for @html_caller_callee;
  print CALLER_CALLEE_OUT "</pre>\n";

#------------------------------------------------------------------------------
# Get the acknowledgement, return to main link, and final html statements.
#------------------------------------------------------------------------------
  $html_home            = ${ generate_home_link ("left") };
  $html_acknowledgement = ${ create_html_credits () };
  $html_end             = ${ terminate_html_document () };

  print CALLER_CALLEE_OUT $html_home;
  print CALLER_CALLEE_OUT "<br>\n";
  print CALLER_CALLEE_OUT $html_acknowledgement;
  print CALLER_CALLEE_OUT $html_end;

  close (CALLER_CALLEE_OUT);

  $msg = "the caller-callee information has been generated";
  gp_message ("verbose", $subr_name, $msg);

  return (0);

} #-- End of subroutine generate_caller_callee

#------------------------------------------------------------------------------
# Generate the html version of the disassembly file.
#
# Note to self (TBD)
# https://community.intel.com/t5/Intel-oneAPI-AI-Analytics/bd-p/ai-analytics-toolkit
#------------------------------------------------------------------------------
sub generate_dis_html
{
  my $subr_name = get_my_name ();

  my ($target_function_ref, $number_of_metrics_ref, $function_info_ref,
      $function_address_and_index_ref, $outputdir_ref, $func_ref,
      $source_line_ref, $metric_ref, $addressobj_index_ref) = @_;

  my $target_function            = ${ $target_function_ref };
  my $number_of_metrics          = ${ $number_of_metrics_ref };
  my @function_info              = @{ $function_info_ref };
  my %function_address_and_index = %{ $function_address_and_index_ref };
  my $outputdir                  = ${ $outputdir_ref };
  my $func                       = ${ $func_ref };
  my @source_line                = @{ $source_line_ref };
  my @metric                     = @{ $metric_ref };
  my %addressobj_index           = %{ $addressobj_index_ref };

  my $dec_instruction_start;
  my $dec_instruction_end;
  my $hex_instruction_start;
  my $hex_instruction_end;

  my @colour_line = ();
  my $hot_line;
  my $metric_values;
  my $src_line;
  my $dec_instr_address;
  my $instruction;
  my $operands;

  my $html_new_line = "<br>";
  my $add_new_line_before;
  my $add_new_line_after;
  my $address_key;
  my $boldface;
  my $file;
  my $filename = $func;
  my $func_name;
  my $orig_hex_instr_address;
  my $hex_instr_address;
  my $index_string;
  my $input_metric;
  my $linenumber;
  my $name;
  my $last_address;
  my $last_address_in_hex;

  my $file_title;
  my $html_header;
  my $html_home;
  my $html_end;

  my $branch_regex      = $g_arch_specific_settings{"regex"};
  my $convert_to_dot    = $g_locale_settings{"convert_to_dot"};
  my $decimal_separator = $g_locale_settings{"decimal_separator"};
  my $hp_value          = $g_user_settings{"highlight_percentage"}{"current_value"};
  my $linksubexp        = $g_arch_specific_settings{"linksubexp"};
  my $subexp            = $g_arch_specific_settings{"subexp"};

  my $file_is_empty;

  my %branch_target = ();
  my %branch_target_no_ref = ();
  my @disassembly_file = ();
  my %extended_branch_target = ();
  my %inverse_branch_target = ();
  my @metrics = ();
  my @modified_html = ();

  my $branch_target_ref;
  my $extended_branch_target_ref;
  my $branch_target_no_ref_ref;

  my $branch_address;
  my $dec_branch_address;
  my $found_it;
  my $found_it_ref;
  my $func_name_in_dis_file;
  my $hex_branch_target;
  my $instruction_address;
  my $instruction_offset;
  my $link;
  my $modified_line;
  my $raw_hex_branch_target;
  my $src_line_ref;
  my $threshold_line;
  my $html_dis_out = $func . ".html";

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $call_regex = '.*([0-9a-fA-F]*):\s+(call)\s*0x([0-9a-fA-F]+)';
  my $line_of_interest_regex = '^#*\s+([\d' . $decimal_separator . '\s+]+)\[\s*(\d+|\?)\]';
  my $white_space_regex = '\s+';
  my $first_integer_regex = '^\d+$';
  my $integer_regex = '\d+';
  my $qmark_regex = '\?';
  my $src_regex = '(\s*)(\d+)\.(.*)';
  my $function_regex = '^(\s*)<Function:\s(.*)>';
  my $end_src_header_regex = "(^\\s+)(\\d+)\\.\\s+(.*)";
  my $end_dis_header_regex = "(^\\s+)(<Function: )(.*)>";
  my $control_flow_1_regex = 'j[a-z]+';
  my $control_flow_2_regex = 'call';
  my $control_flow_3_regex = 'ret';

##  my $function_call_regex2 = '(.*)\s+([0-9a-fA-F]*):\s+(call)\s*0x([0-9a-fA-F]+)\s*';
##  my $endbr_regex          = '\.*([0-9a-fA-F]*):\s+(endbr[32|64])';
#------------------------------------------------------------------------------
# Dynamic. Computed below.
#
# TBD: Try to move these up.
#------------------------------------------------------------------------------
  my $dis_regex;
  my $metric_regex;

  gp_message ("debug", $subr_name, "g_branch_regex = $g_branch_regex");
  gp_message ("debug", $subr_name, "call_regex = $call_regex");
  gp_message ("debug", $subr_name, "g_function_call_v2_regex = $g_function_call_v2_regex");

  my $the_title = set_title ($function_info_ref, $func, "disassembly");

  gp_message ("debug", $subr_name, "the_title = $the_title");

  $file_title      = $the_title;
  $html_header     = ${ create_html_header (\$file_title) };
  $html_home       = ${ generate_home_link ("right") };

  push (@modified_html, $html_header);
  push (@modified_html, $html_home);
  push (@modified_html, "<pre>");

#------------------------------------------------------------------------------
# Open the input and output files.
#------------------------------------------------------------------------------
  open (INPUT_DISASSEMBLY, "<", $filename)
    or die ("$subr_name - unable to open disassembly file $filename for reading: '$!'");
  gp_message ("debug", $subr_name , "opened file $filename for reading");

  open (HTML_OUTPUT, ">", $html_dis_out)
    or die ("$subr_name - unable to open file $html_dis_out for writing: '$!'");
  gp_message ("debug", $subr_name , "opened file $html_dis_out for writing");

#------------------------------------------------------------------------------
# Check if the file is empty
#------------------------------------------------------------------------------
  $file_is_empty = is_file_empty ($filename);
  if ($file_is_empty)
    {

#------------------------------------------------------------------------------
# The input file is empty.  Write a message in the html file and exit.
#------------------------------------------------------------------------------
      gp_message ("debug", $subr_name ,"file $filename is empty");

      my $comment = "No disassembly generated by $tool_name - file $filename is empty";
      my $gp_error_file = $outputdir . "gp-listings.err";

      my $html_empty_file_ref = html_text_empty_file (\$comment, \$gp_error_file);
      my @html_empty_file = @{ $html_empty_file_ref };

      print HTML_OUTPUT "$_\n" for @html_empty_file;

      close (HTML_OUTPUT);

      return (\@source_line);
    }
  else
    {

#------------------------------------------------------------------------------
# Read the file into memory.
#------------------------------------------------------------------------------
      chomp (@disassembly_file = <INPUT_DISASSEMBLY>);
      gp_message ("debug", $subr_name ,"read file $filename into memory");
    }

  my $max_length_first_metric = 0;
  my $src_line_no;

#------------------------------------------------------------------------------
# First scan through the assembly listing.
#------------------------------------------------------------------------------
  for (my $line_no=0; $line_no <= $#disassembly_file; $line_no++)
    {
      my $input_line = $disassembly_file[$line_no];
      gp_message ("debugXL", $subr_name, "[line $line_no] $input_line");

      if ($input_line =~ /$line_of_interest_regex/)
        {

#------------------------------------------------------------------------------
# Found a matching line.  Examples are:
#      0.370                [37]   4021d1:  addsd  %xmm0,%xmm1
#   ## 1.001                [36]   4021d5:  add    $0x1,%rax
#------------------------------------------------------------------------------
          gp_message ("debugXL", $subr_name, "selected line \$1 = $1 \$2 = $2");

          if (defined ($2) and defined($1))
            {
              @metrics = split (/$white_space_regex/ ,$1);
              $src_line_no = $2;
            }
          else
            {
              my $msg = "$input_line has an unexpected format";
              gp_message ("assertion", $subr_name, $msg);
            }

#------------------------------------------------------------------------------
# Compute the maximum length of the first metric and pad the field from the
# left later on.  The fractional part is ignored.
#------------------------------------------------------------------------------
          my $first_metric = $metrics[0];
          my $new_length;
          if ($first_metric =~ /$first_integer_regex/)
            {
              $new_length = length ($first_metric);
            }
          else
            {
              my @fields = split (/$decimal_separator/, $first_metric);
              $new_length = length ($fields[0]);
            }
          $max_length_first_metric = max ($max_length_first_metric, $new_length);
          my $msg;
          $msg = "first_metric = $first_metric " .
                 "max_length_first_metric = $max_length_first_metric";
          gp_message ("debugXL", $subr_name, $msg);

          if ($src_line_no !~ /$qmark_regex/)
#------------------------------------------------------------------------------
# The source code line number is known and is stored.
#------------------------------------------------------------------------------
            {
              $source_line[$line_no] = $src_line_no;
              my $msg;
              $msg  = "found an instruction with a source line ref:";
              $msg .= " source_line[$line_no] = $source_line[$line_no]";
              gp_message ("debugXL", $subr_name, $msg);
            }

#------------------------------------------------------------------------------
# Check for function calls.  If found, get the address offset from $4 and
# compute the target address.
#------------------------------------------------------------------------------
          ($found_it_ref, $branch_target_ref, $extended_branch_target_ref) =
                                                 check_and_proc_dis_func_call (
                                                   \$input_line,
                                                   \$line_no,
                                                   \%branch_target,
                                                   \%extended_branch_target);
          $found_it = ${ $found_it_ref };

          if ($found_it)
            {
              %branch_target = %{ $branch_target_ref };
              %extended_branch_target = %{ $extended_branch_target_ref };
            }

#------------------------------------------------------------------------------
# Look for a branch instruction, or the special endbr32/endbr64 instruction
# that is also considered to be a branch target.  Note that the latter is x86
# specific.
#------------------------------------------------------------------------------
          ($found_it_ref, $branch_target_ref, $extended_branch_target_ref,
           $branch_target_no_ref_ref) = check_and_proc_dis_branches (
                                               \$input_line,
                                               \$line_no,
                                               \%branch_target,
                                               \%extended_branch_target,
                                               \%branch_target_no_ref);
          $found_it = ${ $found_it_ref };

          if ($found_it)
            {
              %branch_target = %{ $branch_target_ref };
              %extended_branch_target = %{ $extended_branch_target_ref };
              %branch_target_no_ref = %{ $branch_target_no_ref_ref };
            }
        }
    } #-- End of loop over line_no

  %inverse_branch_target = reverse (%extended_branch_target);

  gp_message ("debug", $subr_name, "generated inverse of branch target structure");
  gp_message ("debug", $subr_name, "completed parsing file $filename");

  for my $key (sort keys %branch_target)
    {
      gp_message ("debug", $subr_name, "branch_target{$key} = $branch_target{$key}");
    }
  for my $key (sort keys %extended_branch_target)
    {
      gp_message ("debug", $subr_name, "extended_branch_target{$key} = $extended_branch_target{$key}");
    }
  for my $key (sort keys %inverse_branch_target)
    {
      gp_message ("debug", $subr_name, "inverse_branch_target{$key} = $inverse_branch_target{$key}");
    }
  for my $key (sort keys %branch_target_no_ref)
    {
      gp_message ("debug", $subr_name, "branch_target_no_ref{$key} = $branch_target_no_ref{$key}");
      $inverse_branch_target{$key} = $key;
    }
  for my $key (sort keys %inverse_branch_target)
    {
      gp_message ("debug", $subr_name, "inverse_branch_target{$key} = $inverse_branch_target{$key}");
    }

#------------------------------------------------------------------------------
# Process the disassembly.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Dynamically generate the regexes.
#------------------------------------------------------------------------------
  $metric_regex = '';
  for my $metric_used (1 .. $number_of_metrics)
    {
      $metric_regex .= '(\d+' . $decimal_separator . '*\d*)\s+';
    }

  $dis_regex  = '^(#{2}|\s{2})\s+';
  $dis_regex .= '(.*)';
##  $dis_regex .= '\[\s*([0-9?]+)\]\s+([0-9a-fA-F]+):\s+([a-z0-9]+)\s+(.*)';
  $dis_regex .= '\[\s*([0-9?]+)\]\s+([0-9a-fA-F]+):\s+([a-z0-9]+)(.*)';

  gp_message ("debugXL", $subr_name, "metric_regex = $metric_regex");
  gp_message ("debugXL", $subr_name, "dis_regex    = $dis_regex");
  gp_message ("debugXL", $subr_name, "src_regex    = $src_regex");
  gp_message ("debugXL", $subr_name, "contents of lines array");

#------------------------------------------------------------------------------
# Identify the header lines.  Make the minimal assumptions.
#
# In both cases, the first line after the header has whitespace.  This is
# followed by:
#
# - A source line file has "<line_no>."
# - A dissasembly file has "<Function:"
#
# These are the characteristics we use below.
#------------------------------------------------------------------------------
  for (my $line_no=0; $line_no <= $#disassembly_file; $line_no++)
    {
      my $input_line = $disassembly_file[$line_no];
      gp_message ("debugXL", $subr_name, "[line $line_no] $input_line");

      if ($input_line =~ /$end_src_header_regex/)
        {
          gp_message ("debugXL", $subr_name, "header time is over - hit source line\n");
          gp_message ("debugXL", $subr_name, "$1 $2 $3\n");
          last;
        }
      if ($input_line =~ /$end_dis_header_regex/)
        {
          gp_message ("debugXL", $subr_name, "header time is over - hit disassembly line\n");
          last;
        }
      push (@modified_html, "<i>" . $input_line . "</i>");
    }
  my $line_index = scalar (@modified_html);
  gp_message ("debugXL", $subr_name, "final line_index = $line_index");

  for (my $line_no=0; $line_no <= $line_index-1; $line_no++)
    {
      my $msg = " modified_html[$line_no] = $modified_html[$line_no]";
      gp_message ("debugXL", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Source line:
#  20.       for (int64_t r=0; r<repeat_count; r++) {
#
# Disassembly:
#    0.340                [37]   401fec:  addsd   %xmm0,%xmm1
# ## 1.311                [36]   401ff0:  addq    $1,%rax
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Find the hot PCs and store them.
#------------------------------------------------------------------------------
  my @hot_program_counters = ();
  my @transposed_hot_pc = ();
  my @max_metric_values = ();

  gp_message ("debug", $subr_name, "determine the maximum metric values");
  for (my $line_no=$line_index-1; $line_no <= $#disassembly_file; $line_no++)
    {
      my $input_line = $disassembly_file[$line_no];

      if ( $input_line =~ /$dis_regex/ )
        {
##          if ( defined ($1) and defined ($2) and defined ($3) and
##               defined ($4) and defined ($5) and defined ($6) )
          if ( defined ($1) and defined ($2) and defined ($3) and
               defined ($4) and defined ($5) )
            {
              $hot_line      = $1;
              $metric_values = $2;
              $src_line      = $3;
              $dec_instr_address = bigint::hex ($4);
              $instruction   = $5;
              if (defined ($6))
                {
                  my $white_space_regex = '\s*';
                  $operands = $6;
                  $operands =~ s/$white_space_regex//;
                }

              if ($hot_line eq "##")
                {
                  my @metrics = split (" ", $metric_values);
                  push (@hot_program_counters, [@metrics]);
                }
            }
        }
    }
  for my $row (keys @hot_program_counters)
    {
      my $msg = "$filename row[" . $row . "] =";
      for my $col (keys @{$hot_program_counters[$row]})
        {
          $msg .= " $hot_program_counters[$row][$col]";
          $transposed_hot_pc[$col][$row] = $hot_program_counters[$row][$col];
        }
      gp_message ("debugXL", $subr_name, "hot PC = $msg");
    }
  for my $row (keys @transposed_hot_pc)
    {
      my $msg = "$filename row[" . $row . "] =";
      for my $col (keys @{$transposed_hot_pc[$row]})
        {
          $msg .= " $transposed_hot_pc[$row][$col]";
        }
      gp_message ("debugXL", $subr_name, "$filename transposed = $msg");
    }
#------------------------------------------------------------------------------
# Get the maximum metric values and if integer, convert to floating-point.
# Since it is easier, we transpose the array and access it over the columns.
#------------------------------------------------------------------------------
  for my $row (0 .. $#transposed_hot_pc)
    {
      my $max_val = 0;
      for my $col (0 .. $#{$transposed_hot_pc[$row]})
        {
          $max_val = max ($transposed_hot_pc[$row][$col], $max_val);
        }
      if ($max_val =~ /$integer_regex/)
        {
          $max_val = sprintf ("%f", $max_val);
        }
      gp_message ("debugXL", $subr_name, "$filename row = $row max_val = $max_val");
      push (@max_metric_values, $max_val);
    }

    for my $metric (0 .. $#max_metric_values)
      {
        my $msg = "$filename maximum[$metric] = $max_metric_values[$metric]";
        gp_message ("debugM", $subr_name, $msg);
      }

#------------------------------------------------------------------------------
# TBD - Integrate this better.
#
# Scan the instructions to find the instruction address range.  This is used
# to determine if a branch is external to this function.
#------------------------------------------------------------------------------
  $dec_instruction_start = undef;
  $dec_instruction_end   = undef;
  for (my $line_no=$line_index-1; $line_no <= $#disassembly_file; $line_no++)
    {
      my $input_line = $disassembly_file[$line_no];
      if ( $input_line =~ /$dis_regex/ )
        {
#          if ( defined ($1) and defined ($2) and defined ($3) and
##               defined ($4) and defined ($5) and defined ($6) )
          if ( defined ($1) and defined ($2) and defined ($3) and
               defined ($4) and defined ($5) )
            {
              $hot_line      = $1;
              $metric_values = $2;
              $src_line      = $3;
              $dec_instr_address = bigint::hex ($4);
              $instruction   = $5;
##              $operands      = $6;
              if (defined ($6))
                {
                  my $white_space_regex = '\s*';
                  $operands = $6;
                  $operands =~ s/$white_space_regex//;
                }

              if (defined ($dec_instruction_start))
                {
                  if ($dec_instr_address < $dec_instruction_start)
                    {
                      $dec_instruction_start = $dec_instr_address;
                    }
                }
              else
                {
                  $dec_instruction_start = $dec_instr_address;
                }
              if (defined ($dec_instruction_end))
                {
                  if ($dec_instr_address > $dec_instruction_end)
                    {
                      $dec_instruction_end = $dec_instr_address;
                    }
                }
              else
                {
                  $dec_instruction_end = $dec_instr_address;
                }
            }
        }
    }

  if (defined ($dec_instruction_start) and defined ($dec_instruction_end))
    {
      $hex_instruction_start = sprintf ("%x", $dec_instruction_start);
      $hex_instruction_end = sprintf ("%x", $dec_instruction_end);

      my $msg;
      $msg = "$filename $func dec_instruction_start = " .
             "$dec_instruction_start (0x$hex_instruction_start)";
      gp_message ("debugXL", $subr_name, $msg);
      $msg = "$filename $func dec_instruction_end   = " .
             "$dec_instruction_end (0x$hex_instruction_end)";
      gp_message ("debugXL", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# This is where all the results from above come together.
#------------------------------------------------------------------------------
  for (my $line_no=$line_index-1; $line_no <= $#disassembly_file; $line_no++)
    {
      my $input_line = $disassembly_file[$line_no];
      gp_message ("debugXL", $subr_name, "input_line[$line_no] = $input_line");
      if ( $input_line =~ /$dis_regex/ )
        {
          gp_message ("debugXL", $subr_name, "found a disassembly line: $input_line");

          if ( defined ($1) and defined ($2) and defined ($3) and
               defined ($4) and defined ($5) )
            {
#                      $branch_target{$hex_branch_target} = 1;
#                      $extended_branch_target{$instruction_address} = $raw_hex_branch_target;
              $hot_line      = $1;
              $metric_values = $2;
              $src_line      = $3;
              $orig_hex_instr_address = $4;
              $instruction   = $5;
##              $operands      = $6;

              my $msg = "disassembly line: $1 $2 $3 $4 $5";
              if (defined ($6))
                {
                  $msg .= " \$6 = $6";
                  my $white_space_regex = '\s*';
                  $operands = $6;
                  $operands =~ s/$white_space_regex//;
                }
              gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Pad the line with the metrics to ensure correct alignment.
#------------------------------------------------------------------------------
              my $the_length;
              my @split_metrics = split (" ", $metric_values);
              my $first_metric = $split_metrics[0];
##              if ($first_metric =~ /^\d+$/)
              if ($first_metric =~ /$first_integer_regex/)
                {
                  $the_length = length ($first_metric);
                }
              else
                {
                  my @fields = split (/$decimal_separator/, $first_metric);
                  $the_length = length ($fields[0]);
                }
              my $spaces = $max_length_first_metric - $the_length;
              my $pad = "";
              for my $p (1 .. $spaces)
                {
                  $pad .= "&nbsp;";
                }
              $metric_values = $pad . $metric_values;
              gp_message ("debugXL", $subr_name, "pad = $pad");
              gp_message ("debugXL", $subr_name, "metric_values = $metric_values");

#------------------------------------------------------------------------------
# Since the instruction address variable may change and because we need the
# original address without html controls, we use a new variable for the
# (potentially) modified address.
#------------------------------------------------------------------------------
              $hex_instr_address   = $orig_hex_instr_address;
              $add_new_line_before = $FALSE;
              $add_new_line_after  = $FALSE;

              if ($src_line eq "?")

#------------------------------------------------------------------------------
# There is no source line number.  Do not add a link.
#------------------------------------------------------------------------------
                {
                  $modified_line = $hot_line . ' ' . $metric_values . ' [' . $src_line . '] ';
                  gp_message ("debugXL", $subr_name, "initialized modified_line = $modified_line");
                }
              else
                {
#------------------------------------------------------------------------------
# There is a source line number.  Mark it as link.
#------------------------------------------------------------------------------
                  $src_line_ref = "[<a href='#line_".$src_line."'>".$src_line."</a>]";
                  gp_message ("debugXL", $subr_name, "src_line_ref = $src_line_ref");
                  gp_message ("debugXL", $subr_name, "hex_instr_address = $hex_instr_address");

                  $modified_line = $hot_line . ' ' . $metric_values . ' ' . $src_line_ref . ' ';
                  gp_message ("debugXL", $subr_name, "initialized modified_line = $modified_line");
                }

#------------------------------------------------------------------------------
# Mark control flow instructions.  Several cases need to be distinguished.
#
# In all cases we give the instruction a specific color, mark it boldface
# and add a new-line after the instruction
#------------------------------------------------------------------------------
              if ( ($instruction =~ /$control_flow_1_regex/)   or
                   ($instruction =~ /$control_flow_2_regex/)   or
                   ($instruction =~ /$control_flow_3_regex/) )
                {
                  gp_message ("debugXL", $subr_name, "instruction = $instruction is a control flow instruction");

                  $add_new_line_after = $TRUE;

                  $boldface = $TRUE;
                  $instruction = color_string ($instruction, $boldface, $g_html_color_scheme{"control_flow"});
                }

              if (exists ($extended_branch_target{$hex_instr_address}))
#------------------------------------------------------------------------------
# This is a branch instruction and we need to add the target address.
#
# In case the target address is outside of this load object, the link is
# colored differently.
#
# TBD: Add the name and if possible, a working link to this code.
#------------------------------------------------------------------------------
                {
                  $branch_address = $extended_branch_target{$hex_instr_address};

                  $dec_branch_address = bigint::hex ($branch_address);

                  if ( ($dec_branch_address >= $dec_instruction_start) and
                       ($dec_branch_address <= $dec_instruction_end) )
#------------------------------------------------------------------------------
# The instruction is within the range.
#------------------------------------------------------------------------------
                    {
                      $link = "[ <a href='#".$branch_address."'>".$branch_address."</a> ]";
                    }
                  else
                    {
#------------------------------------------------------------------------------
# The instruction is outside of the range.  Change the color of the link.
#------------------------------------------------------------------------------
                      gp_message ("debugXL", $subr_name, "address is outside of range");

                      $link = "[ <a href='#".$branch_address;
                      $link .= "' style='color:$g_html_color_scheme{'link_outside_range'}'>";
                      $link .= $branch_address."</a> ]";
                    }
                  gp_message ("debugXL", $subr_name, "address exists new link = $link");

                  $operands .= ' ' . $link;
                  gp_message ("debugXL", $subr_name, "update #1 modified_line = $modified_line");
                }
              if (exists ($branch_target_no_ref{$hex_instr_address}))
                {
                  gp_message ("debugXL", $subr_name, "NEWBR branch_target_no_ref{$hex_instr_address} = $branch_target_no_ref{$hex_instr_address}");
                }
##              if (exists ($inverse_branch_target{$hex_instr_address}) or
##                  exists ($branch_target_no_ref{$hex_instr_address}))
              if (exists ($inverse_branch_target{$hex_instr_address}))
#------------------------------------------------------------------------------
# This is a target address and we need to define the instruction address to be
# a label.
#------------------------------------------------------------------------------
                {
                  $add_new_line_before = $TRUE;

                  my $branch_target = $inverse_branch_target{$hex_instr_address};
                  my $target = "<a name='".$hex_instr_address."'><b>".$hex_instr_address."</b></a>:";
                  gp_message ("debugXL", $subr_name, "inverse exists - hex_instr_address = $hex_instr_address");
                  gp_message ("debugXL", $subr_name, "inverse exists - add a target target = $target");

                  $hex_instr_address = "<a name='".$hex_instr_address."'><b>".$hex_instr_address."</b></a>";
                  gp_message ("debugXL", $subr_name, "update #2 hex_instr_address = $hex_instr_address");
                  gp_message ("debugXL", $subr_name, "update #2 modified_line     = $modified_line");
                }

              $modified_line .= $hex_instr_address . ': ' . $instruction . ' ' . $operands;

              gp_message ("debugXL", $subr_name, "final modified_line = $modified_line");

#------------------------------------------------------------------------------
# This is a control flow instruction, but it is the last one and we do not
# want to add a newline.
#------------------------------------------------------------------------------
              gp_message ("debugXL", $subr_name, "decide where the <br> should go in the html");
              gp_message ("debugXL", $subr_name, "add_new_line_after  = $add_new_line_after");
              gp_message ("debugXL", $subr_name, "add_new_line_before = $add_new_line_before");

              if ( $add_new_line_after and ($orig_hex_instr_address eq $hex_instruction_end) )
                {
                  $add_new_line_after = $FALSE;
                  gp_message ("debugXL", $subr_name, "$instruction is the last instruction - do not add a newline");
                }

              if ($add_new_line_before)
                {

#------------------------------------------------------------------------------
# Get the previous line, if any, so that we can check what it is.
#------------------------------------------------------------------------------
                  my $prev_line = pop (@modified_html);
                  if ( defined ($prev_line) )
                    {
                      gp_message ("debugXL", $subr_name, "prev_line = $prev_line");

#------------------------------------------------------------------------------
# Restore the previously popped line.
#------------------------------------------------------------------------------
                      push (@modified_html, $prev_line);
                      if ($prev_line ne $html_new_line)
                        {
                          gp_message ("debugXL", $subr_name, "add_new_line_before = $add_new_line_before pushed $html_new_line");
#------------------------------------------------------------------------------
# There is no new-line yet, so add it.
#------------------------------------------------------------------------------
                          push (@modified_html, $html_new_line);
                        }
                      else
                        {
#------------------------------------------------------------------------------
# It was a new-line, so do nothing and continue.
#------------------------------------------------------------------------------
                          gp_message ("debugXL", $subr_name, "need to restore $html_new_line");
                        }
                    }
                }
#------------------------------------------------------------------------------
# Add the newly created line.
#------------------------------------------------------------------------------

              if ($hot_line eq "##")
#------------------------------------------------------------------------------
# Highlight the most expensive line.
#------------------------------------------------------------------------------
                {
                  $modified_line = set_background_color_string (
                                 $modified_line,
                                 $g_html_color_scheme{"background_color_hot"});
                }
#------------------------------------------------------------------------------
# Sub-highlight the lines close enough to the hot line.
#------------------------------------------------------------------------------
              else
                {
                  my @current_metrics = split (" ", $metric_values);
                  for my $metric (0 .. $#current_metrics)
                    {
                      my $current_value;
                      my $max_value;
                      $current_value = $current_metrics[$metric];
#------------------------------------------------------------------------------
# As part of the padding process, non-breaking spaces may have been inserted
# in an earlier phase.  Temporarily remove these to make sure that the maximum
# metric values can be computed.
#------------------------------------------------------------------------------
                      $current_value =~ s/&nbsp;//g;
                      if (exists ($max_metric_values[$metric]))
                        {
                          $max_value     = $max_metric_values[$metric];
                          gp_message ("debugXL", $subr_name, "metric = $metric current_value = $current_value max_value = $max_value");
                          if ( ($max_value > 0) and ($current_value > 0) and ($current_value != $max_value) )
                            {
# TBD: abs needed?
                              gp_message ("debugXL", $subr_name, "metric = $metric current_value = $current_value max_value = $max_value");
                              my $relative_distance = 1.00 - abs ( ($max_value - $current_value)/$max_value );
                              gp_message ("debugXL", $subr_name, "relative_distance = $relative_distance");
                              if (($hp_value > 0) and ($relative_distance >= $hp_value/100.0))
                                {
                                  gp_message ("debugXL", $subr_name, "metric $metric is within the relative_distance");
                                  gp_message ("debugXL", $subr_name, "change bg modified_line = $modified_line");
                                  $modified_line = set_background_color_string (
                                                     $modified_line,
                                                     $g_html_color_scheme{"background_color_lukewarm"});
                                  last;
                                }
                            }
                        }
                    }
                }

##  my @max_metric_values = ();
              push (@modified_html, $modified_line);
              if ($add_new_line_after)
                {
                  gp_message ("debugXL", $subr_name, "add_new_line_after = $add_new_line_after pushed $html_new_line");
                  push (@modified_html, $html_new_line);
                }

            }
          else
            {
              my $msg = "parsing line $input_line";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
      elsif ( $input_line =~ /$src_regex/ )
        {
          if ( defined ($1) and defined ($2) )
            {
####### BUG?
              gp_message ("debugXL", $subr_name, "found a source code line: $input_line");
              gp_message ("debugXL", $subr_name, "\$1 = $1");
              gp_message ("debugXL", $subr_name, "\$2 = $2");
              gp_message ("debugXL", $subr_name, "\$3 = $3");
              my $blanks        = $1;
              my $src_line      = $2;
              my $src_code      = $3;

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
              $src_code =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

              my $target = "<a name='line_".$src_line."'>".$src_line.".</a>";
              gp_message ("debugXL", $subr_name, "src target = $target $src_code");

              my $modified_line = $blanks . $target . $src_code;
              gp_message ("debugXL", $subr_name, "modified_line = $modified_line");
              push (@modified_html, $modified_line);
            }
          else
            {
              my $msg = "parsing line $input_line";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
      elsif ( $input_line =~ /$function_regex/ )
        {
          my $html_name;
          if (defined ($1) and defined ($2))
            {
              $func_name_in_dis_file = $2;
              my $spaces = $1;
              my $boldface = $TRUE;
              gp_message ("debugXL", $subr_name, "function_name = $2");
              my $function_line       = "&lt;Function: " . $func_name_in_dis_file . ">";

##### HACK

              if ($func_name_in_dis_file eq $target_function)
                {
                  my $color_function_name = color_string (
                                 $function_line,
                                 $boldface,
                                 $g_html_color_scheme{"target_function_name"});
                  my $label = "<a id=\"" . $g_function_tag_id{$target_function} . "\"></a>";
                  $html_name = $label . $spaces . "<i>" . $color_function_name . "</i>";
                }
              else
                {
                  my $color_function_name = color_string (
                             $function_line,
                             $boldface,
                             $g_html_color_scheme{"non_target_function_name"});
                  $html_name = "<i>" . $spaces . $color_function_name . "</i>";
                }
              push (@modified_html, $html_name);
            }
          else
            {
              my $msg = "parsing line $input_line";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
    }

#------------------------------------------------------------------------------
# Add an extra line with diagnostics.
#
# TBD: The same is done in process_source but should be done only once.
#------------------------------------------------------------------------------
  if ($hp_value > 0)
    {
      my $rounded_percentage = sprintf ("%.1f", $hp_value);
      $threshold_line = "<i>The setting for the highlight percentage";
      $threshold_line .= " (--highlight-percentage) option:";
      $threshold_line .= " " . $rounded_percentage . " (%)</i>";
    }
  else
    {
      $threshold_line  = "<i>The highlight percentage feature has not been";
      $threshold_line .= " enabled</i>";
    }

  $html_home = ${ generate_home_link ("left") };
  $html_end  = ${ terminate_html_document () };

  push (@modified_html, "</pre>");
  push (@modified_html, $html_new_line);
  push (@modified_html, $threshold_line);
  push (@modified_html, $html_home);
  push (@modified_html, $html_new_line);
  push (@modified_html, $g_html_credits_line);
  push (@modified_html, $html_end);

  for my $i (0 .. $#modified_html)
    {
      gp_message ("debugXL", $subr_name, "[$i] -> $modified_html[$i]");
    }

  for my $i (0 .. $#modified_html)
    {
      print HTML_OUTPUT "$modified_html[$i]" . "\n";
    }

  close (HTML_OUTPUT);
  close (INPUT_DISASSEMBLY);

  gp_message ("debug", $subr_name, "output is in file $html_dis_out");
  gp_message ("debug", $subr_name ,"completed processing disassembly");

  undef %branch_target;
  undef %extended_branch_target;
  undef %inverse_branch_target;

  return (\@source_line, \@metric);

} #-- End of subroutine generate_dis_html

#------------------------------------------------------------------------------
# Generate all the function level information.
#------------------------------------------------------------------------------
sub generate_function_level_info
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref, $call_metrics, $summary_metrics, $input_string,
      $sort_fields_ref) = @_;

  my @exp_dir_list = @{ $exp_dir_list_ref };
  my @sort_fields  = @{ $sort_fields_ref };

  my $expr_name;
  my $first_metric;
  my $gp_display_text_cmd;
  my $gp_functions_cmd;
  my $ignore_value;
  my $msg;
  my $script_pc_metrics;

  my $outputdir      = append_forward_slash ($input_string);

  my $script_file_PC = $outputdir."gp-script-PC";
  my $result_file    = $outputdir."gp-out-PC.err";
  my $gp_error_file  = $outputdir."gp-out-PC.err";
  my $func_limit     = $g_user_settings{func_limit}{current_value};

#------------------------------------------------------------------------------
# The number of entries in the Function Overview includes <Total>, but that is
# not a concern to the user and we add "1" to compensate for this.
#------------------------------------------------------------------------------
  $func_limit += 1;

  gp_message ("debug", $subr_name, "increased the local value for func_limit = $func_limit");

  $expr_name = join (" ", @exp_dir_list);

  gp_message ("debug", $subr_name, "expr_name = $expr_name");

  for my $i (0 .. $#sort_fields)
    {
       gp_message ("debug", $subr_name, "sort_fields[$i] = $sort_fields[$i]");
    }

# Ruud $count = 0;

  gp_message ("debug", $subr_name, "calling $GP_DISPLAY_TEXT to get function information files");

  open (SCRIPT_PC, ">", $script_file_PC)
    or die ("$subr_name - unable to open script file $script_file_PC for writing: '$!'");
  gp_message ("debug", $subr_name, "opened file $script_file_PC for writing");

#------------------------------------------------------------------------------
# Get the list of functions.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Get the first metric.
#------------------------------------------------------------------------------
  $summary_metrics   =~ /^([^:]+)/;
  $first_metric      = $1;
  $g_first_metric    = $1;
  $script_pc_metrics = "address:$summary_metrics";

  gp_message ("debugXL", $subr_name, "$func_limit");
  gp_message ("debugXL", $subr_name, "$summary_metrics");
  gp_message ("debugXL", $subr_name, "$first_metric");
  gp_message ("debugXL", $subr_name, "$script_pc_metrics");

# Temporarily disabled   print SCRIPT_PC "# limit $func_limit\n";
# Temporarily disabled  print SCRIPT_PC "limit $func_limit\n";
  print SCRIPT_PC "# thread_select all\n";
  print SCRIPT_PC "thread_select all\n";

#------------------------------------------------------------------------------
# Empty header.
# TBD: Is still needed? Also, add the header command.
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."header\n";
  print SCRIPT_PC "outfile $outputdir"."header\n";

#------------------------------------------------------------------------------
# Else the output from the next line goes to last sort.func
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."gp-metrics-functions-PC\n";
  print SCRIPT_PC "outfile $outputdir"."gp-metrics-functions-PC\n";
  print SCRIPT_PC "# metrics $script_pc_metrics\n";
  print SCRIPT_PC "metrics $script_pc_metrics\n";
#------------------------------------------------------------------------------
# Not really sorted
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."functions.sort.func-PC\n";
  print SCRIPT_PC "outfile $outputdir"."functions.sort.func-PC\n";
  print SCRIPT_PC "# functions\n";
  print SCRIPT_PC "functions\n";

  print SCRIPT_PC "# outfile $outputdir"."functions.sort.func-PC2\n";
  print SCRIPT_PC "outfile $outputdir"."functions.sort.func-PC2\n";
  print SCRIPT_PC "# metrics address:name:$summary_metrics\n";
  print SCRIPT_PC "metrics address:name:$summary_metrics\n";
  print SCRIPT_PC "# sort $first_metric\n";
  print SCRIPT_PC "sort $first_metric\n";
  print SCRIPT_PC "# functions\n";
  print SCRIPT_PC "functions\n";
#------------------------------------------------------------------------------
# Go through all the possible metrics and sort by each of them.
#------------------------------------------------------------------------------
  for my $field (@sort_fields)
    {
      gp_message ("debug", $subr_name, "sort_fields field = $field");
#------------------------------------------------------------------------------
# Else the output from the next line goes to last sort.func
#------------------------------------------------------------------------------
      print SCRIPT_PC "# outfile $outputdir"."gp-metrics-".$field."-PC\n";
      print SCRIPT_PC "outfile $outputdir"."gp-metrics-".$field."-PC\n";
      print SCRIPT_PC "# metrics $script_pc_metrics\n";
      print SCRIPT_PC "metrics $script_pc_metrics\n";
      print SCRIPT_PC "# outfile $outputdir".$field.".sort.func-PC\n";
      print SCRIPT_PC "outfile $outputdir".$field.".sort.func-PC\n";
      print SCRIPT_PC "# sort $field\n";
      print SCRIPT_PC "sort $field\n";
      print SCRIPT_PC "# functions\n";
      print SCRIPT_PC "functions\n";

      print SCRIPT_PC "# metrics address:name:$summary_metrics\n";
      print SCRIPT_PC "metrics address:name:$summary_metrics\n";
      print SCRIPT_PC "# outfile $outputdir".$field.".sort.func-PC2\n";
      print SCRIPT_PC "outfile $outputdir".$field.".sort.func-PC2\n";
      print SCRIPT_PC "# sort $field\n";
      print SCRIPT_PC "sort $field\n";
      print SCRIPT_PC "# functions\n";
      print SCRIPT_PC "functions\n";
    }

#------------------------------------------------------------------------------
# Get caller-callee list
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile " . $outputdir."caller-callee-PC2\n";
  print SCRIPT_PC "outfile " . $outputdir."caller-callee-PC2\n";
  print SCRIPT_PC "# metrics address:name:$summary_metrics\n";
  print SCRIPT_PC "metrics address:name:$summary_metrics\n";
  print SCRIPT_PC "# callers-callees\n";
  print SCRIPT_PC "callers-callees\n";
#------------------------------------------------------------------------------
# Else the output from the next line goes to last sort.func
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."gp-metrics-calls-PC\n";
  print SCRIPT_PC "outfile $outputdir"."gp-metrics-calls-PC\n";
#------------------------------------------------------------------------------
# TBD: fix the situation that call_metrics is empty.
#------------------------------------------------------------------------------
  if ($call_metrics ne "")
    {
      $script_pc_metrics = "address:$call_metrics";
    }
  else
    {
      $script_pc_metrics = "address";
      $msg = "warning: call_metrics is empty - only address field printed";
      gp_message ("debug", $subr_name, $msg);
    }
  print SCRIPT_PC "# metrics $script_pc_metrics\n";
  print SCRIPT_PC "metrics $script_pc_metrics\n";

#------------------------------------------------------------------------------
# Not really sorted
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."calls.sort.func-PC\n";
  print SCRIPT_PC "outfile $outputdir"."calls.sort.func-PC\n";

#------------------------------------------------------------------------------
# Get caller-callee list
#------------------------------------------------------------------------------
  print SCRIPT_PC "# callers-callees\n";
  print SCRIPT_PC "callers-callees\n";

#------------------------------------------------------------------------------
# Else the output from the next line goes to last sort.func
#------------------------------------------------------------------------------
  print SCRIPT_PC "# outfile $outputdir"."gp-metrics-calltree-PC\n";
  print SCRIPT_PC "outfile $outputdir"."gp-metrics-calltree-PC\n";
  print SCRIPT_PC "# metrics $script_pc_metrics\n";
  print SCRIPT_PC "metrics $script_pc_metrics\n";

  if ($g_user_settings{"calltree"}{"current_value"} eq "on")
    {
      gp_message ("verbose", $subr_name, "Generate the file with the calltree information");
#------------------------------------------------------------------------------
# Get calltree list
#------------------------------------------------------------------------------
      print SCRIPT_PC "# outfile $outputdir"."calltree.sort.func-PC\n";
      print SCRIPT_PC "outfile $outputdir"."calltree.sort.func-PC\n";
      print SCRIPT_PC "# calltree\n";
      print SCRIPT_PC "calltree\n";
    }

#------------------------------------------------------------------------------
# Get the default set of metrics
#------------------------------------------------------------------------------
  my $full_metrics_ref;
  my $all_metrics;
  my $full_function_view = $outputdir . "functions.full";

  $full_metrics_ref = get_all_the_metrics (\$expr_name, \$outputdir);

  $all_metrics  = "address:name:";
  $all_metrics .= ${$full_metrics_ref};
  gp_message ("debug", $subr_name, "all_metrics = $all_metrics");
#------------------------------------------------------------------------------
# Get the name, address, and full overview of all metrics for all functions
#------------------------------------------------------------------------------
   print SCRIPT_PC "# limit 0\n";
   print SCRIPT_PC "limit 0\n";
   print SCRIPT_PC "# metrics $all_metrics\n";
   print SCRIPT_PC "metrics $all_metrics\n";
   print SCRIPT_PC "# thread_select all\n";
   print SCRIPT_PC "thread_select all\n";
   print SCRIPT_PC "# sort default\n";
   print SCRIPT_PC "sort default\n";
   print SCRIPT_PC "# outfile $full_function_view\n";
   print SCRIPT_PC "outfile $full_function_view\n";
   print SCRIPT_PC "# functions\n";
   print SCRIPT_PC "functions\n";

  close (SCRIPT_PC);

  $result_file    = $outputdir."gp-out-PC.err";
  $gp_error_file  = $outputdir.$g_gp_error_logfile;

  $gp_functions_cmd  = "$GP_DISPLAY_TEXT -limit $func_limit ";
  $gp_functions_cmd .= "-viewmode machine -compare off ";
  $gp_functions_cmd .= "-script $script_file_PC $expr_name";

  gp_message ("debug", $subr_name, "calling $GP_DISPLAY_TEXT to get function level information");

  $gp_display_text_cmd = "$gp_functions_cmd 1> $result_file 2>> $gp_error_file";

  gp_message ("debugXL", $subr_name,"cmd = $gp_display_text_cmd");

  my ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
    }

#------------------------------------------------------------------------------
# Parse the full function view and store the data.
#------------------------------------------------------------------------------
  my @input_data = ();
  my $empty_line_regex = '^\s*$';

##  my $full_function_view = $outputdir . "functions.full";

  open (ALL_FUNC_DATA, "<", $full_function_view)
    or die ("$subr_name - unable to open output file $full_function_view for reading '$!'");
  gp_message ("debug", $subr_name, "opened file $full_function_view for reading");

  chomp (@input_data = <ALL_FUNC_DATA>);

  my $start_scanning = $FALSE;
  for (my $line = 0; $line <= $#input_data; $line++)
    {
      my $input_line = $input_data[$line];

      $input_line =~ s/ --  no functions found//;
      $input_data[$line] =~ s/ --  no functions found//;

      $msg = "line = " . $line . " input_line = " . $input_line;
      gp_message ("debugXL", $subr_name, $msg);

#      if ($input_line =~ /^<Total>\s+.*/)
      if ($input_line =~ /\s*(\d+:0x[a-fA-F0-9]+)\s+(\S+)\s+(.*)/)
        {
          $start_scanning = $TRUE;
        }
      elsif ($input_line =~ /$empty_line_regex/)
        {
          $start_scanning = $FALSE;
        }

      if ($start_scanning)
        {
          gp_message ("debugXL", $subr_name, "$line: $input_data[$line]");

          push (@g_full_function_view_table, $input_data[$line]);

          my $hex_address;
          my $full_hex_address = $1;
          my $routine = $2;
          my $all_metrics = $3;
          if ($full_hex_address =~ /(\d+):0x(\S+)/)
            {
              $hex_address = "0x" . $2;
            }
          $g_function_view_all{$routine}{"hex_address"} = $hex_address;
          $g_function_view_all{$routine}{"all_metrics"} = $all_metrics;
        }
    }

  for my $i (keys %g_function_view_all)
    {
      gp_message ("debugXL", $subr_name, "key = $i $g_function_view_all{$i}{'hex_address'} $g_function_view_all{$i}{'all_metrics'}");
    }

  for my $i (keys @g_full_function_view_table)
    {
      gp_message ("debugXL", $subr_name, "g_full_function_view_table[$i] = $i $g_full_function_view_table[$i]");
    }

  return ($script_pc_metrics);

} #-- End of subroutine generate_function_level_info

#------------------------------------------------------------------------------
# Generate all the files needed for the function view.
#------------------------------------------------------------------------------
sub generate_function_view
{
  my $subr_name = get_my_name ();

  my ($directory_name_ref, $summary_metrics_ref, $number_of_metrics_ref,
      $function_info_ref, $function_view_structure_ref, $function_address_info_ref,
      $sort_fields_ref, $exp_dir_list_ref, $addressobjtextm_ref) = @_;

  my $directory_name          = ${ $directory_name_ref };
  my @function_info           = @{ $function_info_ref };
  my %function_view_structure = %{ $function_view_structure_ref };
  my $summary_metrics         = ${ $summary_metrics_ref };
  my $number_of_metrics       = ${ $number_of_metrics_ref };
  my %function_address_info   = %{ $function_address_info_ref };
  my @sort_fields             = @{ $sort_fields_ref };
  my @exp_dir_list            = @{ $exp_dir_list_ref };
  my %addressobjtextm         = %{ $addressobjtextm_ref };

  my @abs_path_exp_dirs = ();
  my @experiment_directories;

  my $target_function;
  my $html_line;
  my $ftag;
  my $routine_length;
  my %html_source_functions = ();

  my $href_link;
  my $infile;
  my $input_experiments;
  my $keep_value;
  my $loadobj;
  my $address_field;
  my $address_offset;
  my $msg;
  my $exe;
  my $extra_field;
  my $new_target_function;
  my $file_title;
  my $html_output_file;
  my $html_function_view;
  my $overview_file;
  my $exp_name;
  my $exp_type;
  my $html_header;
  my $routine;
  my $length_header;
  my $length_metrics;
  my $full_index_line;
  my $acknowledgement;
  my @full_function_view_line = ();
  my $spaces;
  my $size_text;
  my $position_text;
  my $html_first_metric_file;
  my $html_new_line = "<br>";
  my $html_acknowledgement;
  my $html_end;
  my $html_home;
  my $page_title;
  my $html_title_header;

  my $outputdir         = append_forward_slash ($directory_name);
  my $LANG              = $g_locale_settings{"LANG"};
  my $decimal_separator = $g_locale_settings{"decimal_separator"};

  $input_experiments = join (", ", @exp_dir_list);

  for my $i (0 .. $#exp_dir_list)
    {
      my $dir = get_basename ($exp_dir_list[$i]);
      push @abs_path_exp_dirs, $dir;
    }
  $input_experiments = join (", ", @abs_path_exp_dirs);

  gp_message ("debug", $subr_name, "input_experiments = $input_experiments");

#------------------------------------------------------------------------------
# TBD: This should be done only once and much earlier.
#------------------------------------------------------------------------------
  @experiment_directories = split (",", $input_experiments);

#------------------------------------------------------------------------------
# For every function in the function overview, set up an html structure with
# the various hyperlinks.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Core loop that generates an HTML line for each function.
#------------------------------------------------------------------------------
  my $top_of_table = $FALSE;
  for my $i (0 .. $#function_info)
    {
      if (defined ($function_info[$i]{"alt_name"}))
        {
          $target_function = $function_info[$i]{"alt_name"};
        }
      else
        {
          my $msg = "function_info[$i]{\"alt_name\"} is not defined";
          gp_message ("assertion", $subr_name, $msg);
        }

      $html_source_functions{$target_function} = $function_info[$i]{"html function block"};
    }

  for my $i (sort keys %html_source_functions)
    {
      gp_message ("debugXL", $subr_name, "html_source_functions{$i} = $html_source_functions{$i}");
    }

  $file_title = "Function view for experiments " . $input_experiments;

#------------------------------------------------------------------------------
# Example input file:

# Current metrics: address:name:e.totalcpu:e.cycles:e+insts:e+llm
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Functions sorted by metric: Exclusive Total CPU Time
#
# PC Addr.        Name              Excl.     Excl. CPU  Excl.         Excl.
#                                   Total     Cycles     Instructions  Last-Level
#                                   CPU sec.   sec.      Executed      Cache Misses
#  1:0x00000000   <Total>           3.502     4.005      15396819700   24024250
#  2:0x000021ae   mxv_core          3.342     3.865      14500538981   23824045
#  6:0x0003af50   erand48_r         0.080     0.084        768240570          0
#  2:0x00001f7b   init_data         0.040     0.028         64020043     200205
#  6:0x0003b160   __drand48_iterate 0.020     0.                   0          0
#  ...
#------------------------------------------------------------------------------

  for my $metric (@sort_fields)
    {
      $overview_file = $outputdir . $metric . ".sort.func-PC2";

      $exp_type = $metric;

      if ($metric eq "functions")
        {
          $html_function_view .= $g_html_base_file_name{"function_view"} . ".html";
        }
      else
        {
          $html_function_view = $g_html_base_file_name{"function_view"} . "." . $metric . ".html";
        }
#------------------------------------------------------------------------------
# The default function view is based upon the first metric in the list.  We use
# this file in the index.html file.
#------------------------------------------------------------------------------
      if ($metric eq $g_first_metric)
        {
          $html_first_metric_file = $html_function_view;
          my $txt = "g_first_metric = $g_first_metric ";
          $txt   .= "html_first_metric_file = $html_first_metric_file";
          gp_message ("debugXL", $subr_name, $txt);
        }

      $html_output_file = $outputdir . $html_function_view;

      open (FUNCTION_VIEW, ">", $html_output_file)
        or die ("$subr_name - unable to open file $html_output_file for writing - '$!'");
      gp_message ("debug", $subr_name, "opened file $html_output_file for writing");

      $html_home       = ${ generate_home_link ("right") };
      $html_header     = ${ create_html_header (\$file_title) };

      $page_title    = "Function View";
      $size_text     = "h2";
      $position_text = "center";
      $html_title_header = ${ generate_a_header (\$page_title, \$size_text, \$position_text) };

      print FUNCTION_VIEW $html_header;
      print FUNCTION_VIEW $html_home;
      print FUNCTION_VIEW $html_title_header;
      print FUNCTION_VIEW "$_" for @g_html_experiment_stats;
      print FUNCTION_VIEW $html_new_line . "\n";

      my $function_view_structure_ref = process_function_overview (
                                          \$metric,
                                          \$exp_type,
                                          \$summary_metrics,
                                          \$number_of_metrics,
                                          \@function_info,
                                          \%function_view_structure,
                                          \$overview_file);

      my %function_view_structure = %{ $function_view_structure_ref };

#------------------------------------------------------------------------------
# Core part: extract the true function name and find the html code for it.
#------------------------------------------------------------------------------
      gp_message ("debugXL", $subr_name, "the final table");

      print FUNCTION_VIEW "<pre>\n";
      print FUNCTION_VIEW "$_\n" for @{ $function_view_structure{"header"} };

      my $max_length_header  = $function_view_structure{"max header length"};
      my $max_length_metrics = $function_view_structure{"max metrics length"};

#------------------------------------------------------------------------------
# Add 4 more spaces for the distance to the function names.  Purely cosmetic.
#------------------------------------------------------------------------------
      my $pad    = max ($max_length_metrics, $max_length_header) + 4;
      my $spaces = "";
      for my $i (1 .. $pad)
        {
          $spaces .= "&nbsp;";
        }

#------------------------------------------------------------------------------
# Add extra space for the /blank/*/ marker!
#------------------------------------------------------------------------------
      $spaces .= "&nbsp;";
      my $func_header = $spaces . $function_view_structure{"table name"};
      gp_message ("debugXL", $subr_name, "func_header = " . $func_header);

      print FUNCTION_VIEW $spaces . "<b>" .
                          $function_view_structure{"table name"} .
                          "</b>" . $html_new_line . "\n";

#------------------------------------------------------------------------------
# If the header is longer than the metrics, add spaces to padd the difference.
# Also add the same 4 spaces between the metric values and the function name.
#------------------------------------------------------------------------------
      $pad = 0;
      if ($max_length_header > $max_length_metrics)
        {
          $pad = $max_length_header - $max_length_metrics;
        }
      $pad += 4;
      $spaces = "";
      for my $i (1 .. $pad)
        {
          $spaces .= "&nbsp;";
        }

#------------------------------------------------------------------------------
# This is where it literally all comes together.  The metrics and function
# parts are combined.
#------------------------------------------------------------------------------
##      for my $i (keys @{ $function_view_structure{"function table"} })
      for my $i (0 .. $#{ $function_view_structure{"function table"} })
        {
          my $p1 = $function_view_structure{"metrics part"}[$i];
          my $p2 = $function_view_structure{"function table"}[$i];

          $full_index_line = $p1 . $spaces . $p2;

          push (@full_function_view_line, $full_index_line);
        }

      print FUNCTION_VIEW "$_\n" for @full_function_view_line;

#------------------------------------------------------------------------------
# Clear the array before filling it up again.
#------------------------------------------------------------------------------
      @full_function_view_line = ();

#------------------------------------------------------------------------------
# Get the acknowledgement, return to main link, and final html statements.
#------------------------------------------------------------------------------
      $html_home            = ${ generate_home_link ("left") };
      $html_acknowledgement = ${ create_html_credits () };
      $html_end             = ${ terminate_html_document () };

      print FUNCTION_VIEW "</pre>\n";
      print FUNCTION_VIEW $html_home;
      print FUNCTION_VIEW $html_new_line . "\n";
      print FUNCTION_VIEW $html_acknowledgement;
      print FUNCTION_VIEW $html_end;

      close (FUNCTION_VIEW);
    }

  return (\$html_first_metric_file);

} #-- End of subroutine generate_function_view

#------------------------------------------------------------------------------
# Generate an html line that links back to index.html.  The text can either
# be positioned to the left or to the right.
#------------------------------------------------------------------------------
sub generate_home_link
{
  my $subr_name = get_my_name ();

  my ($which_side) = @_;

  my $html_home_line;

  if (($which_side ne "left") and ($which_side ne "right"))
    {
      my $msg = "which_side = $which_side not supported";
      gp_message ("assertion", $subr_name, $msg);
    }

  $html_home_line .= "<div class=\"" . $which_side . "\">";
  $html_home_line .= "<br><a href='" . $g_html_base_file_name{"index"};
  $html_home_line .= ".html' style='background-color:";
  $html_home_line .= $g_html_color_scheme{"index"};
  $html_home_line .= "'><b>Return to main view</b></a>";
  $html_home_line .= "</div>";

  return (\$html_home_line);

} #-- End of subroutine generate_home_link

#------------------------------------------------------------------------------
# Generate a block of html for this function block.
#------------------------------------------------------------------------------
sub generate_html_function_blocks
{
  my $subr_name = get_my_name ();

  my (
  $index_start_ref,
  $index_end_ref,
  $hex_addresses_ref,
  $the_metrics_ref,
  $length_first_metric_ref,
  $special_marker_ref,
  $the_function_name_ref,
  $separator_ref,
  $number_of_metrics_ref,
  $data_function_block_ref,
  $function_info_ref,
  $function_view_structure_ref) = @_;

  my $index_start = ${ $index_start_ref };
  my $index_end   = ${ $index_end_ref };
  my @hex_addresses = @{ $hex_addresses_ref };
  my @the_metrics     = @{ $the_metrics_ref };
  my @length_first_metric = @{ $length_first_metric_ref };
  my @special_marker = @{ $special_marker_ref };
  my @the_function_name = @{ $the_function_name_ref};

  my $separator               = ${ $separator_ref };
  my $number_of_metrics       = ${ $number_of_metrics_ref };
  my $data_function_block     = ${ $data_function_block_ref };
  my @function_info           = @{ $function_info_ref };
  my %function_view_structure = %{ $function_view_structure_ref };

  my $decimal_separator = $g_locale_settings{"decimal_separator"};

  my @html_block_prologue = ();
  my @html_code_function_block = ();
  my @function_lines           = ();
  my @fields = ();
  my @address_field = ();
  my @metric_values = ();
  my @function_names = ();
  my @final_function_names = ();
  my @marker = ();
  my @split_number = ();
  my @function_tags = ();

  my $all_metrics;
  my $current_function_name;
  my $no_of_fields;
  my $name_regex;
  my $full_hex_address;
  my $hex_address;
  my $target_function;
  my $marker_function;
  my $routine;
  my $routine_length;
  my $metrics_length;
  my $max_metrics_length = 0;
  my $modified_line;
  my $string_length;
  my $addr_offset;
  my $current_address;
  my $found_a_match;
  my $ref_index;
  my $alt_name;
  my $length_first_field;
  my $gap;
  my $ipad;
  my $html_line;
  my $target_tag;
  my $tag_for_header;
  my $href_file;
  my $found_alt_name;
  my $name_in_header;
  my $create_hyperlinks;

  state $first_call = $TRUE;
  state $reference_length;

#------------------------------------------------------------------------------
# If the length of the first metric is less than the maximum over all first
# metrics, add spaces to the left to ensure correct alignment.
#------------------------------------------------------------------------------
  for my $k ($index_start .. $index_end)
    {
      my $pad = $g_max_length_first_metric - $length_first_metric[$k];
      if ($pad ge 1)
        {
          my $spaces = "";
          for my $s (1 .. $pad)
            {
              $spaces .= "&nbsp;";
            }
          $the_metrics[$k] = $spaces . $the_metrics[$k];

          my $msg = "padding spaces = $spaces the_metrics[$k] = $the_metrics[$k]";
          gp_message ("debugXL", $subr_name, $msg);
        }

##      my $end_game = "end game3=> pad = $pad" . $hex_addresses[$k] . " " . $the_metrics[$k] . " " . $special_marker[$k] . $the_function_name[$k];
##      gp_message ("debugXL", $subr_name, $end_game);
    }

#------------------------------------------------------------------------------
# An example what @function_lines should look like after the split:
# <empty>
# 6:0x0003ad20   drand48           0.100     0.084        768240570          0
# 6:0x0003af50  *erand48_r         0.080     0.084        768240570          0
# 6:0x0003b160   __drand48_iterate 0.020     0.                   0          0
#------------------------------------------------------------------------------
  @function_lines = split ($separator, $data_function_block);

#------------------------------------------------------------------------------
# Parse the individual lines.  Replace multi-occurrence functions by their
# unique alternative name and mark the target function.
#
# The above split operation produces an empty first field because the line
# starts with the separator.  This is why skip the first field.
#------------------------------------------------------------------------------
  for my $i ($index_start .. $index_end)
    {
      my $input_line = $the_metrics[$i];

      gp_message ("debugXL", $subr_name, "the_metrics[$i] = ". $the_metrics[$i]);

#------------------------------------------------------------------------------
# In case the last metric is 0. only, we append 3 extra characters that
# represent zero.  We cannot change the number to 0.000 though because that
# has a different interpretation than 0.
# In a later phase, the "ZZZ" symbol will be removed again, but for now it
# creates consistency in, for example, the length of the metrics part.
#------------------------------------------------------------------------------
      if ($input_line =~ /[\w0-9$decimal_separator]*(0$decimal_separator$)/)
        {
          if (defined ($1) )
            {
              my $decimal_point = $decimal_separator;
              $decimal_point =~ s/\\//;
              my $txt = "input_line = $input_line = ended with 0";
              $txt   .= $decimal_point;
              gp_message ("debugXL", $subr_name, $txt);

              $the_metrics[$i] .= "ZZZ";
            }
        }

      $hex_address     = $hex_addresses[$i];
      $marker_function = $special_marker[$i];
      $routine         = $the_function_name[$i];
#------------------------------------------------------------------------------
# Get the length of the metrics line before ZZZ is replaced by spaces.
#------------------------------------------------------------------------------
      $all_metrics     = $the_metrics[$i];
      $metrics_length  = length ($all_metrics);
      $all_metrics     =~ s/ZZZ/&nbsp;&nbsp;&nbsp;/g;

      $max_metrics_length = max ($max_metrics_length, $metrics_length);

      push (@marker, $marker_function);
      push (@address_field, $hex_address);
      push (@metric_values, $all_metrics);
      push (@function_names, $routine);

      my $index_into_function_info_ref = get_index_function_info (
                                         \$routine,
                                         \$hex_addresses[$i],
                                         $function_info_ref);

      my $index_into_function_info = ${ $index_into_function_info_ref };
      $target_tag = $function_info[$index_into_function_info]{"tag_id"};
      $alt_name = $function_info[$index_into_function_info]{"alt_name"};

#------------------------------------------------------------------------------
# Keep the name of the target function (the one marked with a *) for later use.
# This is the tag that identifies the block in the caller-callee output.  The
# tag is used in the link to the caller-callee in the function overview.
#------------------------------------------------------------------------------
      if ($marker_function eq "*")
        {
          $tag_for_header = $target_tag;
          $name_in_header = $alt_name;

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
          $name_in_header =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

        }
      push (@final_function_names, $alt_name);
      push (@function_tags, $target_tag);

      gp_message ("debugXL", $subr_name, "index_into_function_info = $index_into_function_info");
      gp_message ("debugXL", $subr_name, "target_tag = $target_tag");
      gp_message ("debugXL", $subr_name, "alt_name   = $alt_name");

    } #-- End of loop for my $i ($index_start .. $index_end)

  my $tag_line = "<a id='" . $tag_for_header . "'></a>";
  $html_line  = "<br>\n";
  $html_line .= $tag_line . "Function name: ";
  $html_line .= "<span style='color:" . $g_html_color_scheme{"target_function_name"} . "'>";
  $html_line .= "<b>" . $name_in_header . "</b></span>\n";
  $html_line .= "<br>";

  push (@html_block_prologue, $html_line);

  gp_message ("debugXL", $subr_name, "the final function block for $name_in_header");

  $href_file = $g_html_base_file_name{"caller_callee"} . ".html";

#------------------------------------------------------------------------------
# Process the function blocks and generate the HTML structure for them.
#------------------------------------------------------------------------------
  for my $i (0 .. $#final_function_names)
    {
      $current_function_name = $final_function_names[$i];
      gp_message ("debugXL", $subr_name, "current_function_name = $current_function_name");

#------------------------------------------------------------------------------
# Do not add hyperlinks for <Total>.
#------------------------------------------------------------------------------
      if ($current_function_name eq "<Total>")
        {
          $create_hyperlinks = $FALSE;
        }
      else
        {
          $create_hyperlinks = $TRUE;
        }

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
      $current_function_name =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

      $html_line = $metric_values[$i] . " ";

      if ($marker[$i] eq "*")
        {
          $current_function_name = "<b>" . $current_function_name . "</b>";
        }
      $html_line .= " <a href='" . $href_file . "#" . $function_tags[$i] . "'>" . $current_function_name . "</a>";

      if ($marker[$i] eq "*")
        {
            $html_line = "<br>" . $html_line;
        }
      elsif (($marker[$i] ne "*") and ($i == 0))
        {
            $html_line = "<br>" . $html_line;
        }

      gp_message ("debugXL", $subr_name, "html_line = $html_line");

#------------------------------------------------------------------------------
# Find the index into "function_info" for this particular function.
#------------------------------------------------------------------------------
      $routine         = $function_names[$i];
      $current_address = $address_field[$i];

      my $target_index_ref = find_index_in_function_info (\$routine, \$current_address, \@function_info);
      my $target_index     = ${ $target_index_ref };

      gp_message ("debugXL", $subr_name, "routine = $routine current_address = $current_address target_index = $target_index");

#------------------------------------------------------------------------------
# TBD Do this once for each function and store the result.  This is a saving
# because functions may and typically will appear more than once.
#------------------------------------------------------------------------------
      my $spaces_left = $function_view_structure{"max function length"} - $function_info[$target_index]{"function length"};

#------------------------------------------------------------------------------
# Add the links to the line. Make sure there is at least one space.
#------------------------------------------------------------------------------
      my $spaces = "&nbsp;";
      for my $k (1 .. $spaces_left)
        {
          $spaces .= "&nbsp;";
        }

      if ($create_hyperlinks)
        {
          $html_line .= $spaces;
          $html_line .= $function_info[$target_index]{"href_source"};
          $html_line .= "&nbsp;";
          $html_line .= $function_info[$target_index]{"href_disassembly"};
        }

      push (@html_code_function_block, $html_line);
    }

    for my $lines (0 .. $#html_code_function_block)
      {
        gp_message ("debugXL", $subr_name, "final html block = " . $html_code_function_block[$lines]);
      }

  return (\@html_block_prologue, \@html_code_function_block);

} #-- End of subroutine generate_html_function_blocks

#------------------------------------------------------------------------------
# Get all the metrics available
#
# (gprofng-display-text) metric_list
# Current metrics: e.totalcpu:i.totalcpu:e.cycles:e+insts:e+llm:name
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Available metrics:
#          Exclusive Total CPU Time: e.%totalcpu
#          Inclusive Total CPU Time: i.%totalcpu
#              Exclusive CPU Cycles: e.+%cycles
#              Inclusive CPU Cycles: i.+%cycles
#   Exclusive Instructions Executed: e+%insts
#   Inclusive Instructions Executed: i+%insts
# Exclusive Last-Level Cache Misses: e+%llm
# Inclusive Last-Level Cache Misses: i+%llm
#  Exclusive Instructions Per Cycle: e+IPC
#  Inclusive Instructions Per Cycle: i+IPC
#  Exclusive Cycles Per Instruction: e+CPI
#  Inclusive Cycles Per Instruction: i+CPI
#                              Size: size
#                        PC Address: address
#                              Name: name
#------------------------------------------------------------------------------
sub get_all_the_metrics
{
  my $subr_name = get_my_name ();

  my ($experiments_ref, $outputdir_ref) = @_;

  my $experiments = ${ $experiments_ref };
  my $outputdir   = ${ $outputdir_ref };

  my $ignore_value;
  my $gp_functions_cmd;
  my $gp_display_text_cmd;

  my $metrics_output_file = $outputdir . "metrics-all";
  my $result_file   = $outputdir . $g_gp_output_file;
  my $gp_error_file = $outputdir . $g_gp_error_logfile;
  my $script_file_metrics = $outputdir . "script-metrics";

  my @metrics_data = ();

  open (SCRIPT_METRICS, ">", $script_file_metrics)
    or die ("$subr_name - unable to open script file $script_file_metrics for writing: '$!'");
  gp_message ("debug", $subr_name, "opened script file $script_file_metrics for writing");

  print SCRIPT_METRICS "# outfile $metrics_output_file\n";
  print SCRIPT_METRICS "outfile $metrics_output_file\n";
  print SCRIPT_METRICS "# metric_list\n";
  print SCRIPT_METRICS "metric_list\n";

  close (SCRIPT_METRICS);

  $gp_functions_cmd  = "$GP_DISPLAY_TEXT -script $script_file_metrics $experiments";

  gp_message ("debug", $subr_name, "calling $GP_DISPLAY_TEXT to get all the metrics");

  $gp_display_text_cmd = "$gp_functions_cmd 1>> $result_file 2>> $gp_error_file";
  gp_message ("debug", $subr_name, "cmd = $gp_display_text_cmd");

  my ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
    }

  open (METRICS_INFO, "<", $metrics_output_file)
    or die ("$subr_name - unable to open file $metrics_output_file for reading '$!'");
  gp_message ("debug", $subr_name, "opened file $metrics_output_file for reading");

#------------------------------------------------------------------------------
# Read the input file into memory.
#------------------------------------------------------------------------------
  chomp (@metrics_data = <METRICS_INFO>);
  gp_message ("debug", $subr_name, "read all contents of file $metrics_output_file into memory");
  gp_message ("debug", $subr_name, "\$#metrics_data = $#metrics_data");

  my $input_line;
  my $ignore_lines_regex = '^(?:Current|Available|\s+Size:|\s+PC Address:|\s+Name:)';
  my $split_line_regex = '(.*): (.*)';
  my $empty_line_regex = '^\s*$';
  my @metric_list_all = ();
  for (my $line_no=0; $line_no <= $#metrics_data; $line_no++)
    {

      $input_line = $metrics_data[$line_no];

##      if ( not (($input_line =~ /$ignore_lines_regex/ or ($input_line =~ /^\s*$/))))
      if ( not ($input_line =~ /$ignore_lines_regex/) and not ($input_line =~ /$empty_line_regex/) )
        {
          if ($input_line =~ /$split_line_regex/)
            {
#------------------------------------------------------------------------------
# Remove the percentages.
#------------------------------------------------------------------------------
              my $metric_definition = $2;
              $metric_definition =~ s/\%//g;
              gp_message ("debug", $subr_name, "line_no = $line_no $metrics_data[$line_no] metric_definition = $metric_definition");
              push (@metric_list_all, $metric_definition);
            }
        }

    }

  gp_message ("debug", $subr_name, "\@metric_list_all = @metric_list_all");

  my $final_list = join (":", @metric_list_all);
  gp_message ("debug", $subr_name, "final_list = $final_list");

  close (METRICS_INFO);

  return (\$final_list);

} #-- End of subroutine get_all_the_metrics

#------------------------------------------------------------------------------
# A simple function to return the basename using fileparse.  To keep things
# simple, a suffixlist is not supported.  In case this is needed, use the
# fileparse function directly.
#------------------------------------------------------------------------------
sub get_basename
{
  my ($full_name) = @_;

  my $ignore_value_1;
  my $ignore_value_2;
  my $basename_value;

  ($basename_value, $ignore_value_1, $ignore_value_2) = fileparse ($full_name);

  return ($basename_value);

} #-- End of subroutine get_basename

#------------------------------------------------------------------------------
# Get the details on the experiments and store these in a file.  Each
# experiment has its own file.  This makes the processing easier.
#------------------------------------------------------------------------------
sub get_experiment_info
{
  my $subr_name = get_my_name ();

  my ($outputdir_ref, $exp_dir_list_ref) = @_;

  my $outputdir    = ${ $outputdir_ref };
  my @exp_dir_list = @{ $exp_dir_list_ref };

  my $cmd_output;
  my $current_slot;
  my $error_code;
  my $exp_info_file;
  my @exp_info       = ();
  my @experiment_data = ();
  my $gp_error_file;
  my $gp_display_text_cmd;
  my $gp_functions_cmd;
  my $gp_log_file;
  my $ignore_value;
  my $msg;
  my $overview_file;
  my $result_file;
  my $script_file;
  my $the_experiments;

  $the_experiments = join (" ", @exp_dir_list);

  $script_file   = $outputdir . "gp-info-exp.script";
  $exp_info_file = $outputdir . "gp-info-exp-list.out";
  $overview_file = $outputdir . "gp-overview.out";
  $gp_log_file   = $outputdir . $g_gp_output_file;
  $gp_error_file = $outputdir . $g_gp_error_logfile;

  open (SCRIPT_EXPERIMENT_INFO, ">", $script_file)
    or die ("$subr_name - unable to open script file $script_file for writing: '$!'");
  gp_message ("debug", $subr_name, "opened script file $script_file for writing");

#------------------------------------------------------------------------------
# Attributed User CPU Time=a.user : for calltree - see P37 in manual
#------------------------------------------------------------------------------
  print SCRIPT_EXPERIMENT_INFO "# compare on\n";
  print SCRIPT_EXPERIMENT_INFO "compare on\n";
  print SCRIPT_EXPERIMENT_INFO "# outfile $exp_info_file\n";
  print SCRIPT_EXPERIMENT_INFO "outfile $exp_info_file\n";
  print SCRIPT_EXPERIMENT_INFO "# exp_list\n";
  print SCRIPT_EXPERIMENT_INFO "exp_list\n";
  print SCRIPT_EXPERIMENT_INFO "# outfile $overview_file\n";
  print SCRIPT_EXPERIMENT_INFO "outfile $overview_file\n";
  print SCRIPT_EXPERIMENT_INFO "# overview\n";
  print SCRIPT_EXPERIMENT_INFO "overview\n";

  close SCRIPT_EXPERIMENT_INFO;

  $gp_functions_cmd = "$GP_DISPLAY_TEXT -script $script_file $the_experiments";

  gp_message ("debug", $subr_name, "executing $GP_DISPLAY_TEXT to get the experiment information");

  $gp_display_text_cmd = "$gp_functions_cmd 1>> $gp_log_file 2>> $gp_error_file";

  ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
    }

#------------------------------------------------------------------------------
# The first file has the following format:
#
# ID Sel     PID Experiment
# == === ======= ======================================================
#  1 yes 2078714 <absolute_path/mxv.hwc.1.thr.er
#  2 yes 2078719 <absolute_path/mxv.hwc.2.thr.er
#------------------------------------------------------------------------------
  open (EXP_INFO, "<", $exp_info_file)
    or die ("$subr_name - unable to open file $exp_info_file for reading '$!'");
  gp_message ("debug", $subr_name, "opened script file $exp_info_file for reading");

  chomp (@exp_info = <EXP_INFO>);

#------------------------------------------------------------------------------
# TBD - Check for the groups to exist below:
#------------------------------------------------------------------------------
  $current_slot = 0;
  for my $i (0 .. $#exp_info)
    {
      my $input_line = $exp_info[$i];

      gp_message ("debug", $subr_name, "$i => exp_info[$i] = $exp_info[$i]");

      if ($input_line =~ /^\s*(\d+)\s+(.+)/)
        {
          my $exp_id    = $1;
          my $remainder = $2;
          $experiment_data[$current_slot]{"exp_id"} = $exp_id;
          $experiment_data[$current_slot]{"exp_data_file"} = $outputdir . "gp-info-exp-" . $exp_id . ".out";
          gp_message ("debug", $subr_name, $i . " " . $exp_id . " " . $remainder);
          if ($remainder =~ /^(\w+)\s+(\d+)\s+(.+)/)
            {
              my $exp_name = $3;
              $experiment_data[$current_slot]{"exp_name_full"} = $exp_name;
              $experiment_data[$current_slot]{"exp_name_short"} = get_basename ($exp_name);
              $current_slot++;
              gp_message ("debug", $subr_name, $i . " " . $1 . " " . $2 . " " . $3);
            }
          else
            {
              $msg = "remainder = $remainder has an unexpected format";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
    }
#------------------------------------------------------------------------------
# The experiment IDs and names are known.  We can now generate the info for
# each individual experiment.
#------------------------------------------------------------------------------
  $gp_log_file   = $outputdir . $g_gp_output_file;
  $gp_error_file = $outputdir . $g_gp_error_logfile;

  $script_file = $outputdir . "gp-details-exp.script";

  open (SCRIPT_EXPERIMENT_DETAILS, ">", $script_file)
    or die ("$subr_name - unable to open script file $script_file for writing: '$!'");
  gp_message ("debug", $subr_name, "opened script file $script_file for writing");

  for my $i (sort keys @experiment_data)
    {
      my $exp_id = $experiment_data[$i]{"exp_id"};

      $result_file = $experiment_data[$i]{"exp_data_file"};

# statistics
# header
      print SCRIPT_EXPERIMENT_DETAILS "# outfile "    . $result_file . "\n";
      print SCRIPT_EXPERIMENT_DETAILS "outfile "      . $result_file . "\n";
      print SCRIPT_EXPERIMENT_DETAILS "# header "     . $exp_id . "\n";
      print SCRIPT_EXPERIMENT_DETAILS "header "       . $exp_id . "\n";
      print SCRIPT_EXPERIMENT_DETAILS "# statistics " . $exp_id . "\n";
      print SCRIPT_EXPERIMENT_DETAILS "statistics "   . $exp_id . "\n";

    }

  close (SCRIPT_EXPERIMENT_DETAILS);

  $gp_functions_cmd = "$GP_DISPLAY_TEXT -script $script_file $the_experiments";

  $msg = "executing $GP_DISPLAY_TEXT to get the experiment details";
  gp_message ("debug", $subr_name, $msg);

  $gp_display_text_cmd = "$gp_functions_cmd 1>> $gp_log_file 2>> $gp_error_file";

  ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
#------------------------------------------------------------------------------
# This is unlikely to happen, but you never know.
#------------------------------------------------------------------------------
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
    }

  return (\@experiment_data);

} #-- End of subroutine get_experiment_info

#------------------------------------------------------------------------------
# This subroutine returns a string of the type "size=<n>", where <n> is the
# size of the file passed in.  If n > 1024, a unit is appended.
#------------------------------------------------------------------------------
sub getfilesize
{
  my $subr_name = get_my_name ();

  my ($filename) = @_;

  my $size;
  my $file_stat;

  if (not -e $filename)
    {
#------------------------------------------------------------------------------
# The return value is used in the caller.  This is why we return the empty
# string in case the file does not exist.
#------------------------------------------------------------------------------
      gp_message ("debug", $subr_name, "filename = $filename not found");
      return ("");
    }
  else
    {
      $file_stat = stat ($filename);
      $size      = $file_stat->size;

      gp_message ("debug", $subr_name, "filename = $filename");
      gp_message ("debug", $subr_name, "size     = $size");

      if ($size > 1024)
        {
          if ($size > 1024*1024)
            {
              $size = $size/1024/1024;
              $size =~ s/\..*//;
              $size = $size."MB";
            }
          else
            {
              $size = $size/1024;
              $size =~ s/\..*//;
              $size = $size."KB";
            }
        }
      else
        {
          $size=$size." bytes";
        }
      gp_message ("debug", $subr_name, "size = $size title=\"$size\"");

      return ("title=\"$size\"");
    }

} #-- End of subroutine getfilesize

#------------------------------------------------------------------------------
# Parse the fsummary output and for all functions, store all the information
# found in "function_info".  In addition to this, several derived structures
# are stored as well, making this structure a "onestop" place to get all the
# info that is needed.
#------------------------------------------------------------------------------
sub get_function_info
{
  my $subr_name = get_my_name ();

  my ($FSUMMARY_FILE) = @_;

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $white_space_regex = '\s*';

  my @function_info              = ();
  my %function_address_and_index = ();
  my %LINUX_vDSO                 = ();
  my %function_view_structure    = ();
  my %addressobjtextm            = ();
#------------------------------------------------------------------------------
# TBD: This structure is no longer used and most likely can be removed.
#------------------------------------------------------------------------------
  my %functions_index             = ();

  my $msg;

# TBD: check
  my $full_address_field;
  my %source_files   = ();

  my $i;
  my $line;
  my $routine_flag;
  my $value;
  my $field;
  my $df_flag;
  my $address_decimal;
  my $routine;

  my $num_source_files           = 0;
  my $number_of_unique_functions = 0;
  my $number_of_non_unique_functions = 0;

  my $function_info_regex   = '\s*(\S+[a-zA-Z\s]*):(.*)';
  my $get_hex_address_regex = '(\d+):(0x\S+)';
#------------------------------------------------------------------------------
# Open the file generated using the -fsummary option.
#------------------------------------------------------------------------------
  $msg = " - unable to open file $FSUMMARY_FILE for reading:";
  open (FSUMMARY_FILE, "<", $FSUMMARY_FILE)
    or die ($subr_name . $msg . " " . $!);
  $msg = "opened file $FSUMMARY_FILE for reading";
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# This is the typical structure of the fsummary output:
#
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Functions sorted by metric: Exclusive Total CPU Time
#
# <Total>
#         Exclusive Total CPU Time: 11.538 (100.0%)
#         Inclusive Total CPU Time: 11.538 (100.0%)
#                             Size:      0
#                       PC Address: 1:0x00000000
#                      Source File: (unknown)
#                      Object File: (unknown)
#                      Load Object: <Total>
#                     Mangled Name:
#                          Aliases:
#
# a_function_name
#         Exclusive Total CPU Time:  4.003 ( 34.7%)
#         Inclusive Total CPU Time:  4.003 ( 34.7%)
#                             Size:    715
#                       PC Address: 2:0x00006c61
#                      Source File: <absolute path to source file>
#                      Object File: <object filename>
#                      Load Object: <executable name>
#                     Mangled Name:
#                          Aliases:
#
# The previous block is repeated for every function.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Skip the header.  The header is defined to end with a blank line.
#------------------------------------------------------------------------------
  while (<FSUMMARY_FILE>)
    {
      $line = $_;
      chomp ($line);
      if ($line =~ /^\s*$/)
        {
          last;
        }
    }

#------------------------------------------------------------------------------
# Process the remaining blocks.  Note that the first line should be <Total>,
# but this is currently not checked.
#------------------------------------------------------------------------------
  $i = 0;
  $routine_flag = $TRUE;
  while (<FSUMMARY_FILE>)
    {
      $line = $_;
      chomp ($line);

#------------------------------------------------------------------------------
# Legacy issue to deal with. Up until somewhere between binutils 2.40 and 2.41,
# gprofng display text might print the " --  no functions found" comment.
# No, the two spaces after -- are not my typo ;-)
#
# Since then, this comment is no longer printed, but the safe approach is to
# remove any occurrence upfront.
#------------------------------------------------------------------------------
      $line =~ s/ --  no functions found//;

      $msg = "line = " . $line;
      gp_message ("debugXL", $subr_name, $msg);

      if ($line =~ /^\s*$/)
#------------------------------------------------------------------------------
# Blank line.
#------------------------------------------------------------------------------
        {
          $routine_flag = $TRUE;
          $df_flag = 0;

#------------------------------------------------------------------------------
# Linux vDSO exception
#
# TBD: Check if still relevant.
#------------------------------------------------------------------------------
          if ($function_info[$i]{"Load Object"} eq "DYNAMIC_FUNCTIONS")
            {
              $LINUX_vDSO{substr ($function_info[$i]{"addressobjtext"},1)} = $function_info[$i]{"routine"};
            }
          $i++;
          next;
        }

      if ($routine_flag)
#------------------------------------------------------------------------------
# Should be the first line after the blank line.
#------------------------------------------------------------------------------
        {
          $routine                      = $line;
          push (@{ $g_map_function_to_index{$routine} }, $i);
          gp_message ("debugXL", $subr_name, "pushed i = $i to g_map_function_to_index{$routine}");

#------------------------------------------------------------------------------
# In a later parsing phase we need to know how many fields there are in a
# function name. For example, "<static>@0x21850 (<libc-2.28.so>)" is name that
# may show up in a function list.
#
# Here we determine the number of fields and store it.
#
# REVISIT This may not be needed anymore
#------------------------------------------------------------------------------
          my @fields_in_name = split (" ", $routine);
          $function_info[$i]{"fields in routine name"} = scalar (@fields_in_name);

#------------------------------------------------------------------------------
# This name may change if the function has multiple occurrences, but in any
# case, at the end of this routine this component has the final name to be
# used.
#------------------------------------------------------------------------------
          $function_info[$i]{"alt_name"} = $routine;
          if (not exists ($g_function_occurrences{$routine}))
            {
              gp_message ("debugXL", $subr_name, "the entry in function_info for $routine does not exist");
              $function_info[$i]{"routine"} = $routine;
              $g_function_occurrences{$routine} = 1;

              gp_message ("debugXL", $subr_name, "g_function_occurrences{$routine} = $g_function_occurrences{$routine}");
            }
          else
            {
              gp_message ("debugXL", $subr_name, "the entry in function_info for $routine exists already");
              $function_info[$i]{"routine"} = $routine;
              $g_function_occurrences{$routine} += 1;
              if (not exists ($g_multi_count_function{$routine}))
                {
                  $g_multi_count_function{$routine} = $TRUE;
                }
              $msg  = "g_function_occurrences{$routine} = ";
              $msg .= $g_function_occurrences{$routine};
              gp_message ("debugXL", $subr_name, $msg);
            }
#------------------------------------------------------------------------------
# New: used when generating the index.
#------------------------------------------------------------------------------
          $function_info[$i]{"function length"} = length ($routine);
          $function_info[$i]{"tag_id"} = create_function_tag ($i);
          if (not exists ($g_function_tag_id{$routine}))
            {
              $g_function_tag_id{$routine} = create_function_tag ($i);
            }
          else
            {

#------------------------------------------------------------------------------
## TBD HACK!!! CHECK!!!!!
#------------------------------------------------------------------------------
              $g_function_tag_id{$routine} = $i;
            }

          $routine_flag = $FALSE;
          gp_message ("debugXL", $subr_name, "stored " . $function_info[$i]{"routine"});

#------------------------------------------------------------------------------
# The $functions_index hash contains an array.  After an initial assignment,
# other values that have been found are pushed onto the arrays.
#------------------------------------------------------------------------------
          if (not exists ($functions_index{$routine}))
            {
              $functions_index{$routine} = [$i];
            }
          else
            {
#------------------------------------------------------------------------------
# Add the array index to the list
#------------------------------------------------------------------------------
              push (@{$functions_index{$routine}}, $i);
            }
          next;
        }

#------------------------------------------------------------------------------
# Example format of an input block, where $line is one of the following:
#         Exclusive Total CPU Time: 0.001 (  0.0%)
#         Inclusive Total CPU Time: 0.001 (  0.0%)
#                             Size:    92
#                       PC Address: 5:0x00125de0
#                      Source File: (unknown)
#                      Object File: (unknown)
#                      Load Object: /usr/lib64/libc-2.28.so
#                     Mangled Name:
#                          Aliases: __brk
#------------------------------------------------------------------------------
      $line =~ s/^\s+//;
      if ($line =~ /$function_info_regex/)
        {
          if (defined ($1) and defined($2))
            {
              $field = $1;
              $value = $2;
              $value =~ s/$g_rm_surrounding_spaces_regex//g;

              $msg = "initial - field = " . $field . " value = " . $value;
              gp_message ("debugM", $subr_name, $msg);
            }
          else
            {
              $msg = "the input line pattern was not recognized";
              gp_message ("warning", $subr_name, $msg);
              gp_message ("debug", $subr_name, $msg);
              $msg = "execution continues, but there may be a problem later";
              gp_message ("warning", $subr_name, $msg);
              gp_message ("debug", $subr_name, $msg);

              $field = "not recognized";
              $value = "not recognized";
            }
#------------------------------------------------------------------------------
# The field has no value.
#------------------------------------------------------------------------------
          if (length ($value) eq 0)
##          if ($value =~ /^\s+$/)
##              if (length ($2) gt 0)
##              if ($2 == " ")
            {
              if ($field eq "Mangled Name")
                {
                  $value = $routine; 

                  $msg =  "no mangled name found - use the routine name ";
                  $msg .= $routine . " as the mangled name";
                  gp_message ("debugM", $subr_name, $msg);
                }
              else
                {
                  $value = "no_value_given";

                  $msg  =  "no value was found for this field - set to ";
                  $msg .=  $value;
                  gp_message ("debugM", $subr_name, $msg);
                }
            }
#------------------------------------------------------------------------------
# Remove any leading whitespace characters.
#------------------------------------------------------------------------------
          $value =~ s/$white_space_regex//;
#------------------------------------------------------------------------------
# These are the final values that will be used.
#------------------------------------------------------------------------------
          $msg = "final - field = " . $field . " value = " . $value;
          gp_message ("debugM", $subr_name, $msg);

          $function_info[$i]{$field} = $value;
        }
##      $value =~ s/$white_space_regex//;

## \s*(\S+[a-zA-Z\s]*):\ *(.*)

###      my @input_fields   = split (":", $line);
###      my $no_of_elements = scalar (@input_fields);

###      gp_message ("debugXL", $subr_name, "#input_fields   = $#input_fields");
###      gp_message ("debugXL", $subr_name, "no_of_elements  = $no_of_elements");
###      gp_message ("debugXL", $subr_name, "input_fields[0] = $input_fields[0]");

###      if ($no_of_elements == 1)
#------------------------------------------------------------------------------
# No value
#------------------------------------------------------------------------------
###         {
###           $whatever = $input_fields[0];
###           $value    = "";
###         }
###       elsif ($no_of_elements == 2)
###         {
### #------------------------------------------------------------------------------
### # Note that $value may consist of multiple fields (e.g. 1.651 ( 95.4%)).
### #------------------------------------------------------------------------------
###           $whatever = $input_fields[0];
###           $value    = $input_fields[1];
###         }
###       elsif ($no_of_elements == 3)
###         {
###           $whatever = $input_fields[0];
### 	  if ($whatever eq "PC Address")
### #------------------------------------------------------------------------------
### # Must be an address field.  Restore the second colon.
### #------------------------------------------------------------------------------
### 	    {
###               $value = $input_fields[1] . ":" . $input_fields[2];
### 	    }
### 	  elsif ($whatever eq "Mangled Name")
### #------------------------------------------------------------------------------
### # The mangled name includes a colon (:).  Just copy the entire string.
### #------------------------------------------------------------------------------
### 	    {
###               $value = $input_fields[2];
### 	    }
###         }
###       else
###         {
### 	  if ($whatever eq "Aliases")
### #------------------------------------------------------------------------------
### # The mangled name includes a colon (:).  Just copy the entire string.
### #------------------------------------------------------------------------------
### 	    {
###               $value = $input_fields[2];
### 	    }
### 	  else
### 	    {
###               $msg = "input line = " . $line;
###               gp_message ("debug", $subr_name, $msg);
###               for my $i (keys @input_fields)
###                 {
###                   $msg = "input_fields[$i] = " . $input_fields[$i];
###                   gp_message ("debug", $subr_name, $msg);
###                 }
###               $msg = "unexpected input: number of fields = " . $no_of_elements;
###               gp_message ("debug", $subr_name, $msg);
### ##              gp_message ("assertion", $subr_name, $msg);
### 	    }
###        }
##      $function_info[$i]{$field} = $value;

#------------------------------------------------------------------------------
# TBD: Seems to be not used anymore and can most likely be removed. Check this.
#------------------------------------------------------------------------------
      if ($field =~ /Source File/)
        {
          if (!exists ($source_files{$value}))
            {
              $source_files{$value} = $TRUE;
              $num_source_files++;
            }
        }

      if ($field =~ /PC Address/)
        {
          my $segment;
          my $offset;
#------------------------------------------------------------------------------
# The format of the address is assumed to be the following 2:0x000070a8
# Note that the regex is pretty wide.  This is from the original code and
# could be made more specific:
#          if ($value =~ /\s*(\S+):(\S+)/)
#------------------------------------------------------------------------------
#          if ($value =~ /\s*(\S+):(\S+)/)
          if ($value =~ /\s*(\d+):0x([0-9a-zA-Z]+)/)
            {
              $segment = $1;
              $offset  = $2;
#------------------------------------------------------------------------------
# Convert to a base 10 number
#------------------------------------------------------------------------------
              $address_decimal = bigint::hex ($offset); # decimal
#------------------------------------------------------------------------------
# Construct the address field.  Note that we use the hex address here.
# For example @2:0x0003f280
#------------------------------------------------------------------------------
              $full_address_field = $segment.":0x".$offset;

              $function_info[$i]{"addressobj"}     = $address_decimal;
              $function_info[$i]{"addressobjtext"} = $full_address_field;
              $addressobjtextm{$full_address_field} = $i; # $RI
            }
          if (not exists ($function_address_and_index{$routine}{$value}))
            {
              $function_address_and_index{$routine}{$value} = $i;

              $msg  = "function_address_and_index{$routine}{$value} = ";
              $msg .= $function_address_and_index{$routine}{$value};
              gp_message ("debugXL", $subr_name, $msg);
            }
          else
            {
              $msg  = "function_info: $FSUMMARY_FILE: function $routine";
              $msg .= " already has a PC Address";
              gp_message ("debugXL", $subr_name, $msg);
            }

          $g_total_function_count++;
        }
    }
  close (FSUMMARY_FILE);

#------------------------------------------------------------------------------
# For every function in the function overview, set up an html structure with
# the various hyperlinks.
#------------------------------------------------------------------------------
  gp_message ("debugXL", $subr_name, "augment function_info with alt_name");
  my $target_function;
  my $html_line;
  my $ftag;
  my $routine_length;
  my %html_source_functions = ();
  for my $i (keys @function_info)
    {
      $target_function = $function_info[$i]{"routine"};

      gp_message ("debugXL", $subr_name, "i = $i target_function = $target_function");

      my $href_link;
##      $href_link  = "<a href=\'file." . $i . ".src.new.html#";
      $href_link  = "<a href=\'file." . $i . ".";
      $href_link .= $g_html_base_file_name{"source"};
      $href_link .= ".html#";
      $href_link .= $function_info[$i]{"tag_id"};
      $href_link .= "\'>source</a>";
      $function_info[$i]{"href_source"} = $href_link;

      $href_link  = "<a href=\'file." . $i . ".";
      $href_link .= $g_html_base_file_name{"disassembly"};
      $href_link .= ".html#";
      $href_link .= $function_info[$i]{"tag_id"};
      $href_link .= "\'>disassembly</a>";
      $function_info[$i]{"href_disassembly"} = $href_link;

      $href_link  = "<a href=\'";
      $href_link .= $g_html_base_file_name{"caller_callee"};
      $href_link .= ".html#";
      $href_link .= $function_info[$i]{"tag_id"};
      $href_link .= "\'>caller-callee</a>";
      $function_info[$i]{"href_caller_callee"} = $href_link;

      gp_message ("debug", $subr_name, "g_function_occurrences{$target_function} = $g_function_occurrences{$target_function}");

      if ($g_function_occurrences{$target_function} > 1)
        {
#------------------------------------------------------------------------------
# In case a function occurs more than one time in the function overview, we
# add the load object and address offset info to make it unique.
#
# This forces us to update some entries in function_info too.
#------------------------------------------------------------------------------
          my $loadobj = $function_info[$i]{"Load Object"};
          my $address_field = $function_info[$i]{"addressobjtext"};
          my $address_offset;

#------------------------------------------------------------------------------
# The address field has the following format: @<n>:<address_offset>
# We only care about the address offset.
#------------------------------------------------------------------------------
          if ($address_field =~ /$get_hex_address_regex/)
            {
              $address_offset = $2;
            }
          else
            {
              my $msg = "failed to extract the address offset from $address_field - use the full field";
              gp_message ("warning", $subr_name, $msg);
              $address_offset = $address_field;
            }
          my $exe = get_basename ($loadobj);
          my $extra_field = " (<" . $exe . " $address_offset" .">)";
###          $target_function .= $extra_field;
          $function_info[$i]{"alt_name"} = $target_function . $extra_field;
          gp_message ("debugXL", $subr_name, "function_info[$i]{\"alt_name\"} = " . $function_info[$i]{"alt_name"});

#------------------------------------------------------------------------------
# Store the length of the function name and get the tag id.
#------------------------------------------------------------------------------
          $function_info[$i]{"function length"} = length ($target_function . $extra_field);
          $function_info[$i]{"tag_id"} = create_function_tag ($i);

          gp_message ("debugXL", $subr_name, "updated function_info[$i]{'routine'} = $function_info[$i]{'routine'}");
          gp_message ("debugXL", $subr_name, "updated function_info[$i]{'alt_name'} = $function_info[$i]{'alt_name'}");
          gp_message ("debugXL", $subr_name, "updated function_info[$i]{'function length'} = $function_info[$i]{'function length'}");
          gp_message ("debugXL", $subr_name, "updated function_info[$i]{'tag_id'} = $function_info[$i]{'tag_id'}");
        }
    }
  gp_message ("debug", $subr_name, "augment function_info with alt_name completed");

#------------------------------------------------------------------------------
# Compute the maximum function name length.
#
# The maximum length is stored in %function_view_structure.
#------------------------------------------------------------------------------
  my $max_function_length = 0;
  for my $i (0 .. $#function_info)
    {
      $max_function_length = List::Util::max ($max_function_length, $function_info[$i]{"function length"});

      gp_message ("debugXL", $subr_name, "function_info[$i]{\"alt_name\"} = " . $function_info[$i]{"alt_name"} . " length = " . $function_info[$i]{"function length"});
    }

#------------------------------------------------------------------------------
# Define the name of the table and take the length into account, since it may
# be longer than the function name(s).
#------------------------------------------------------------------------------
  $function_view_structure{"table name"} = "Function name";

  $max_function_length = max ($max_function_length, length ($function_view_structure{"table name"}));

  $function_view_structure{"max function length"} = $max_function_length;

#------------------------------------------------------------------------------
# Core loop that generates an HTML line for each function.  This line is
# stored in function_info.
#------------------------------------------------------------------------------
  my $top_of_table = $FALSE;
  for my $i (keys @function_info)
    {
      my $new_target_function;

      if (defined ($function_info[$i]{"alt_name"}))
        {
          $target_function = $function_info[$i]{"alt_name"};
          gp_message ("debugXL", $subr_name, "retrieved function_info[$i]{'alt_name'} = $function_info[$i]{'alt_name'}");
        }
      else
        {
          my $msg = "function_info[$i]{\"alt_name\"} is not defined";
          gp_message ("assertion", $subr_name, $msg);
        }

      my $function_length  = $function_info[$i]{"function length"};
      my $number_of_blanks = $function_view_structure{"max function length"} - $function_length;

      my $spaces = "&nbsp;&nbsp;";
      for my $i (1 .. $number_of_blanks)
        {
          $spaces .= "&nbsp;";
        }
      if ($target_function eq "<Total>")
#------------------------------------------------------------------------------
# <Total> is a pseudo function and there is no source, or disassembly for it.
# We could add a link to the caller-callee part, but this is currently not
# done.
#------------------------------------------------------------------------------
        {
          $top_of_table = $TRUE;
          $html_line  = "&nbsp;<b>&lt;Total></b>";
        }
      else
        {
#------------------------------------------------------------------------------
# Add the * symbol as a marker in case the same function occurs multiple times.
# Otherwise insert a space.
#------------------------------------------------------------------------------
          my $base_function_name = $function_info[$i]{"routine"};
          if (exists ($g_function_occurrences{$base_function_name}))
            {
              if ($g_function_occurrences{$base_function_name} > 1)
                {
                  $new_target_function = "*" . $target_function;
                }
              else
                {
                  $new_target_function = "&nbsp;" . $target_function;
                }
            }
          else
            {
              my $msg = "g_function_occurrences{$base_function_name} does not exist";
              gp_message ("assertion", $subr_name, $msg);
            }

#------------------------------------------------------------------------------
# Create the block with the function name, in boldface, plus the links to the
# source, disassembly and caller-callee views.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
          $new_target_function =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

          $html_line  = "<b>$new_target_function</b>" . $spaces;
          $html_line .= $function_info[$i]{"href_source"}      . "&nbsp;";
          $html_line .= $function_info[$i]{"href_disassembly"} . "&nbsp;";
          $html_line .= $function_info[$i]{"href_caller_callee"};
        }

      $msg = "target_function = $target_function html_line = $html_line";
      gp_message ("debugM", $subr_name, $msg);
      $html_source_functions{$target_function} = $html_line;

#------------------------------------------------------------------------------
# TBD: In the future we want to re-use this block elsewhere.
#------------------------------------------------------------------------------
      $function_info[$i]{"html function block"} = $html_line;
    }

  for my $i (keys %html_source_functions)
    {
      $msg = "html_source_functions{$i} = $html_source_functions{$i}";
      gp_message ("debugM", $subr_name, $msg);
    }
  for my $i (keys @function_info)
    {
      $msg  = "function_info[$i]{\"html function block\"} = ";
      $msg .= $function_info[$i]{"html function block"};
      gp_message ("debugM", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Print the key data structure %function_info.  This is a nested hash.
#------------------------------------------------------------------------------
  for my $i (0 .. $#function_info)
    {
      for my $role (sort keys %{ $function_info[$i] })
        {
           $msg  = "on return: function_info[$i]{$role} = ";
           $msg .= $function_info[$i]{$role};
           gp_message ("debugM", $subr_name, $msg);
        }
    }
#------------------------------------------------------------------------------
# Print the data structure %function_address_and_index. This is a nested hash.
#------------------------------------------------------------------------------
  for my $F (keys %function_address_and_index)
    {
      for my $fields (sort keys %{ $function_address_and_index{$F} })
        {
           $msg  = "on return: function_address_and_index{$F}{$fields} = ";
           $msg .= $function_address_and_index{$F}{$fields};
           gp_message ("debugM", $subr_name, $msg);
        }
    }
#------------------------------------------------------------------------------
# Print the data structure %functions_index. This is a hash with an arrray.
#------------------------------------------------------------------------------
  for my $F (keys %functions_index)
    {
      gp_message ("debug", $subr_name, "on return: functions_index{$F} = @{ $functions_index{$F} }");
# alt code      for my $i (0 .. $#{ $functions_index{$F} } )
# alt code        {
# alt code           gp_message ("debug", $subr_name, "on return: \$functions_index{$F} = $functions_index{$F}[$i]");
# alt code        }
    }

#------------------------------------------------------------------------------
# Print the data structure %function_view_structure. This is a hash.
#------------------------------------------------------------------------------
  for my $F (keys %function_view_structure)
    {
      gp_message ("debug", $subr_name, "on return: function_view_structure{$F} = $function_view_structure{$F}");
    }

#------------------------------------------------------------------------------
# Print the data structure %g_function_occurrences and use this structure to
# gather statistics about the functions.
#
# TBD: add this info to the experiment data overview.
#------------------------------------------------------------------------------
  $number_of_unique_functions = 0;
  $number_of_non_unique_functions = 0;
  for my $F (keys %g_function_occurrences)
    {
      gp_message ("debug", $subr_name, "on return: g_function_occurrences{$F} = $g_function_occurrences{$F}");
      if ($g_function_occurrences{$F} == 1)
        {
          $number_of_unique_functions++;
        }
      else
        {
          $number_of_non_unique_functions++;
        }
    }

  for my $i (keys %g_map_function_to_index)
    {
      my $n = scalar (@{ $g_map_function_to_index{$i} });
      gp_message ("debug", $subr_name, "on return: g_map_function_to_index [$n] : $i => @{ $g_map_function_to_index{$i} }");
    }

#------------------------------------------------------------------------------
# TBD: Include this info on the page with experiment data.  Include names
# with multiple occurrences.
#------------------------------------------------------------------------------
  $msg = "Number of source files                            : " .
         $num_source_files;
  gp_message ("debug", $subr_name, $msg);
  $msg = "Total number of functions                         : " .
         $g_total_function_count;
  gp_message ("debug", $subr_name, $msg);
  $msg = "Number of functions with a unique name            : " .
         $number_of_unique_functions;
  gp_message ("debug", $subr_name, $msg);
  $msg = "Number of functions with more than one occurrence : " .
         $number_of_non_unique_functions;
  gp_message ("debug", $subr_name, $msg);
  my $multi_occurrences = $g_total_function_count -
                          $number_of_unique_functions;
  $msg = "Total number of multiple occurences of the same function name : " .
         $multi_occurrences;
  gp_message ("debug", $subr_name, $msg);

  return (\@function_info, \%function_address_and_index, \%addressobjtextm,
          \%LINUX_vDSO, \%function_view_structure);

} #-- End of subroutine get_function_info
#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub get_hdr_info
{
  my $subr_name = get_my_name ();

  my ($outputdir, $file) = @_;

  state $first_call = $TRUE;

  my $ASORTFILE;
  my @HDR;
  my $HDR;
  my $metric;
  my $line;
  my $ignore_directory;
  my $ignore_suffix;
  my $number_of_header_lines;

#------------------------------------------------------------------------------
# Add a "/" to simplify the construction of path names in the remainder.
#------------------------------------------------------------------------------
  $outputdir = append_forward_slash ($outputdir);

# Could get more header info from
# <metric>[e.bit_fcount].sort.func file - etc.

  gp_message ("debug", $subr_name, "input file->$file<-");
#-----------------------------------------------
  if ($file eq $outputdir."calls.sort.func")
    {
      $ASORTFILE=$outputdir."calls";
      $metric = "calls"
    }
  elsif ($file eq $outputdir."calltree.sort.func")
    {
      $ASORTFILE=$outputdir."calltree";
      $metric = "calltree"
    }
  elsif ($file eq $outputdir."functions.sort.func")
    {
      $ASORTFILE=$outputdir."functions.func";
      $metric = "functions";
    }
  else
    {
      $ASORTFILE = $file;
#      $metric = basename ($file,".sort.func");
      ($metric, $ignore_directory,  $ignore_suffix) = fileparse ($file, ".sort.func");
      gp_message ("debug", $subr_name, "ignore_directory = $ignore_directory ignore_suffix = $ignore_suffix");
    }

  gp_message ("debug", $subr_name, "file = $file metric = $metric");

  open (ASORTFILE,"<", $ASORTFILE)
    or die ("$subr_name - unable to open file $ASORTFILE for reading: '$!'");
  gp_message ("debug", $subr_name, "opened file $ASORTFILE for reading");

  $number_of_header_lines = 0;
  while (<ASORTFILE>)
    {
      $line =$_;
      chomp ($line);

      if ($line  =~ /^Current/)
        {
          next;
        }
      if ($line  =~ /^Functions/)
        {
          next;
        }
      if ($line  =~ /^Callers/)
        {
          next;
        }
      if ($line  =~ /^\s*$/)
        {
          next;
        }
      if (!($line  =~ /^\s*\d/))
        {
          $HDR[$number_of_header_lines] = $line;
          $number_of_header_lines++;
          next;
        }
      last;
     }
  close (ASORTFILE);
#------------------------------------------------------------------------------
# Ruud - Fixed a bug. The output should not be appended, but overwritten.
# open (HI,">>$OUTPUTDIR"."hdrinfo");
#------------------------------------------------------------------------------
  my $outfile = $outputdir."hdrinfo";

  if ($first_call)
    {
      $first_call = $FALSE;
      open (HI ,">", $outfile)
        or die ("$subr_name - unable to open file $outfile for writing: '$!'");
      gp_message ("debug", $subr_name, "opened file $outfile for writing");
    }
  else
    {
      open (HI ,">>", $outfile)
        or die ("$subr_name - unable to open file $outfile in append mode: '$!'");
      gp_message ("debug", $subr_name, "opened file $outfile in append mode");
    }

  print HI "\#$metric hdrlines=$number_of_header_lines\n";
  my $len = 0;
  for $HDR (@HDR)
    {
      print HI "$HDR\n";
      gp_message ("debugXL", $subr_name, "HDR = $HDR\n");
    }
  close (HI);
  if ($first_call)
    {
      gp_message ("debug", $subr_name, "wrote file $outfile");
    }
  else
    {
      gp_message ("debug", $subr_name, "updated file $outfile");
    }
#-----------------------------------------------

} #-- End of subroutine get_hdr_info

#------------------------------------------------------------------------------
# Get the home directory and the location(s) of the configuration file on the
# current system.
#------------------------------------------------------------------------------
sub get_home_dir_and_rc_path
{
  my $subr_name = get_my_name ();

  my ($rc_file_name) = @_;

  my @rc_file_paths;
  my $target_cmd;
  my $home_dir;
  my $error_code;

  $target_cmd  = $g_mapped_cmds{"printenv"} . " HOME";

  ($error_code, $home_dir) = execute_system_cmd ($target_cmd);

  if ($error_code != 0)
    {
      my $msg = "cannot find a setting for HOME - please set this";
      gp_message ("assertion", $subr_name, $msg);
    }
  else

#------------------------------------------------------------------------------
# The home directory is known and we can define the locations for the
# configuration file.
#------------------------------------------------------------------------------
    {
      @rc_file_paths = (".", "$home_dir");
    }

  gp_message ("debug", $subr_name, "upon return: \@rc_file_paths = @rc_file_paths");

  return ($home_dir, \@rc_file_paths);

} #-- End of subroutine get_home_dir_and_rc_path

#------------------------------------------------------------------------------
# This subroutine generates a list with the hot functions.
#------------------------------------------------------------------------------
sub get_hot_functions
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref, $summary_metrics, $input_string) = @_;

  my @exp_dir_list = @{ $exp_dir_list_ref };

  my $cmd_output;
  my $error_code;
  my $expr_name;
  my $first_metric;
  my $gp_display_text_cmd;
  my $msg;
  my $ignore_value;

  my @sort_fields = ();

  $expr_name = join (" ", @exp_dir_list);

  gp_message ("debug", $subr_name, "expr_name = $expr_name");

  my $outputdir = append_forward_slash ($input_string);

  my $script_file   = $outputdir."gp-fsummary.script";
  my $outfile       = $outputdir."gp-fsummary.out";
  my $result_file   = $outputdir."gp-fsummary.stderr";
  my $gp_error_file = $outputdir.$g_gp_error_logfile;

  @sort_fields = split (":", $summary_metrics);

#-- RUUD

  $msg = "summary_metrics = " . $summary_metrics;
  gp_message ("debug", $subr_name, $msg);
  for my $field (@sort_fields)
    {
     $msg = "metric field = " . $field; 
     gp_message ("debug", $subr_name, $msg);
    }
#------------------------------------------------------------------------------
# This is extremely unlikely to happen, but if so, it is a fatal error.
#------------------------------------------------------------------------------
  my $number_of_elements = scalar (@sort_fields);

  gp_message ("debug", $subr_name, "number of fields in summary_metrics = $number_of_elements");

  if ($number_of_elements == 0)
    {
      my $msg = "there are $number_of_elements in the metrics list";
      gp_message ("assertion", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Get the summary of the hot functions
#------------------------------------------------------------------------------
  open (SCRIPT, ">", $script_file)
    or die ("$subr_name - unable to open script file $script_file for writing: '$!'");
  gp_message ("debug", $subr_name, "opened script file $script_file for writing");

#------------------------------------------------------------------------------
# TBD: Check what this is about:
# Attributed User CPU Time=a.user : for calltree - see P37 in manual
#------------------------------------------------------------------------------
  print SCRIPT "# limit 0\n";
  print SCRIPT "limit 0\n";
  print SCRIPT "# metrics $summary_metrics\n";
  print SCRIPT "metrics $summary_metrics\n";
  print SCRIPT "# thread_select all\n";
  print SCRIPT "thread_select all\n";

#------------------------------------------------------------------------------
# Use first out of summary metrics as first (it doesn't matter which one)
# $first_metric = (split /:/,$summary_metrics)[0];
#------------------------------------------------------------------------------

  $first_metric = $sort_fields[0];

  print SCRIPT "# outfile $outfile\n";
  print SCRIPT "outfile $outfile\n";
  print SCRIPT "# sort $first_metric\n";
  print SCRIPT "sort $first_metric\n";
  print SCRIPT "# fsummary\n";
  print SCRIPT "fsummary\n";

  close SCRIPT;

  my $gp_functions_cmd = "$GP_DISPLAY_TEXT -viewmode machine -compare off -script $script_file $expr_name";

  gp_message ("debug", $subr_name, "executing $GP_DISPLAY_TEXT to get the list of functions");

  $gp_display_text_cmd = "$gp_functions_cmd 1> $result_file 2>> $gp_error_file";

  ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
      my $msg = "error code = $error_code - failure executing command $gp_display_text_cmd";
      gp_message ("abort", $subr_name, $msg);
    }

  return ($outfile,\@sort_fields);

} #-- End of subroutine get_hot_functions

#------------------------------------------------------------------------------
# For a given function name, return the index into "function_info".  This
# index gives access to all the meta data for the input function.
#------------------------------------------------------------------------------
sub get_index_function_info
{
  my $subr_name = get_my_name ();

  my ($routine_ref, $hex_address_ref, $function_info_ref) = @_;

  my $routine     = ${ $routine_ref };
  my $hex_address = ${ $hex_address_ref };
  my @function_info = @{ $function_info_ref };

  my $alt_name = $routine;
  my $current_address = $hex_address;
  my $found_a_match;
  my $index_into_function_info;
  my $msg;
  my $target_tag;

#------------------------------------------------------------------------------
# Check if this function has multiple occurrences.
#------------------------------------------------------------------------------
  $msg = "check for multiple occurrences";
  gp_message ("debugM", $subr_name, $msg);
  $msg = "target routine name = " . $routine;
  gp_message ("debugM", $subr_name, $msg);

  if (not exists ($g_multi_count_function{$routine}))
    {
#------------------------------------------------------------------------------
# There is only a single occurrence and it is straightforward to get the tag.
#--------------------------------------------------------------------------
##          push (@final_function_names, $routine);
## KANWEG      for my $key (sort keys %g_map_function_to_index)
## KANWEG        {
## KANWEG          $msg = "g_map_function_to_index{". $key . "} = " . $g_map_function_to_index{$key};
## KANWEG          gp_message ("debugXL", $subr_name, $msg);
## KANWEG        }
      if (exists ($g_map_function_to_index{$routine}))
        {
          $index_into_function_info = $g_map_function_to_index{$routine}[0];
        }
      else
        {
          my $msg = "no entry for $routine in g_map_function_to_index";
          gp_message ("assertion", $subr_name, $msg);
        }
    }
  else
    {
#------------------------------------------------------------------------------
# The function name has more than one occurrence and we need to find the one
# that matches with the address.
#------------------------------------------------------------------------------
      $found_a_match = $FALSE;
      gp_message ("debug", $subr_name, "$routine: occurrences = $g_function_occurrences{$routine}");
      for my $ref (keys @{ $g_map_function_to_index{$routine} })
        {
          my $ref_index   = $g_map_function_to_index{$routine}[$ref];
          my $addr_offset = $function_info[$ref_index]{"addressobjtext"};

          gp_message ("debug", $subr_name, "$routine: retrieving duplicate entry at ref_index = $ref_index");
          gp_message ("debug", $subr_name, "$routine: addr_offset = $addr_offset");
 
#------------------------------------------------------------------------------
# TBD: Do this substitution when storing "addressobjtext" in function_info.
#------------------------------------------------------------------------------
          $addr_offset =~ s/^@\d+://;
          gp_message ("debug", $subr_name, "$routine: addr_offset = $addr_offset");
          if ($addr_offset eq $current_address)
            {
              $found_a_match = $TRUE;
              $index_into_function_info = $ref_index;
              last;
            }
        }

#------------------------------------------------------------------------------
# If there is no match, something has gone really wrong and we bail out.
#------------------------------------------------------------------------------
      if (not $found_a_match)
        {
          my $msg = "cannot find the mapping in function_info for function $routine";
          gp_message ("assertion", $subr_name, $msg);
        }
    }

  return (\$index_into_function_info);

} #-- End of subroutine get_index_function_info

#------------------------------------------------------------------------------
# Get the setting for LANG, or assign a default if it is not set.
#------------------------------------------------------------------------------
sub get_LANG_setting
{
  my $subr_name = get_my_name ();

  my $error_code;
  my $lang_setting;
  my $target_cmd;
  my $command_string;
  my $LANG;

  $target_cmd = $g_mapped_cmds{"printenv"};
#------------------------------------------------------------------------------
# Use the printenv command to get the settings for LANG.
#------------------------------------------------------------------------------
  if ($target_cmd eq "road to nowhere")
    {
      $error_code = 1;
    }
  else
    {
      $command_string = $target_cmd . " LANG";
      ($error_code, $lang_setting) = execute_system_cmd ($command_string);
    }

  if ($error_code == 0)
    {
      chomp ($lang_setting);
      $LANG = $lang_setting;
    }
  else
    {
      $LANG = $g_default_setting_lang;
      my $msg = "cannot find a setting for LANG - use a default setting";
      gp_message ("warning", $subr_name, $msg);
    }

  return ($LANG);

} #-- End of subroutine get_LANG_setting

#------------------------------------------------------------------------------
# This subroutine gathers the basic information about the metrics.
#------------------------------------------------------------------------------
sub get_metrics_data
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref, $outputdir, $outfile1, $outfile2, $error_file) = @_;

  my @exp_dir_list = @{ $exp_dir_list_ref };

  my $cmd_options;
  my $cmd_output;
  my $error_code;
  my $expr_name;
  my $metrics_cmd;
  my $metrics_output;
  my $target_cmd;

  $expr_name = join (" ", @exp_dir_list);

  gp_message ("debug", $subr_name, "expr_name = $expr_name");

#------------------------------------------------------------------------------
# Execute the $GP_DISPLAY_TEXT tool with the appropriate options.  The goal is
# to get all the output in files $outfile1 and $outfile2.  These are then
# parsed.
#------------------------------------------------------------------------------
  $cmd_options   = " -viewmode machine -compare off -thread_select all";
  $cmd_options  .= " -outfile $outfile2";
  $cmd_options  .= " -fsingle '<Total>' -metric_list $expr_name";

  $metrics_cmd   = "$GP_DISPLAY_TEXT $cmd_options 1> $outfile1 2> $error_file";

  gp_message ("debug", $subr_name, "command used to gather the information:");
  gp_message ("debug", $subr_name, $metrics_cmd);

  ($error_code, $metrics_output) = execute_system_cmd ($metrics_cmd);

#------------------------------------------------------------------------------
# Error handling.  Any error that occurred is fatal and execution
# should be aborted by the caller.
#------------------------------------------------------------------------------
  if ($error_code == 0)
    {
      gp_message ("debug", $subr_name, "metrics data in files $outfile1 and $outfile2");
    }
  else
    {
      $target_cmd  = $g_mapped_cmds{"cat"} . " $error_file";

      ($error_code, $cmd_output) = execute_system_cmd ($target_cmd);

      chomp ($cmd_output);

      gp_message ("error", $subr_name, "contents of file $error_file:");
      gp_message ("error", $subr_name, $cmd_output);
    }

  return ($error_code);

} #-- End of subroutine get_metrics_data

#------------------------------------------------------------------------------
# Wrapper that returns the last part of the subroutine name.  The assumption is
# that the last part of the input name is of the form "aa::bb" or just "bb".
#------------------------------------------------------------------------------
sub get_my_name
{
  my $called_by = (caller (1))[3];
  my @parts     = split ("::", $called_by);
  return ($parts[$#parts]);

##  my ($the_full_name_ref) = @_;

##  my $the_full_name = ${ $the_full_name_ref };
##  my $last_part;

#------------------------------------------------------------------------------
# If the regex below fails, use the full name."
#------------------------------------------------------------------------------
##  $last_part = $the_full_name;

#------------------------------------------------------------------------------
# Capture the last part if there are multiple parts separated by "::".
#------------------------------------------------------------------------------
##  if ($the_full_name =~ /.*::(.+)$/)
##    {
##      if (defined ($1))
##        {
##          $last_part = $1;
##        }
##    }

##  return (\$last_part);

} #-- End of subroutine get_my_name

#------------------------------------------------------------------------------
# Determine the characteristics of the current system
#------------------------------------------------------------------------------
sub get_system_config_info
{
#------------------------------------------------------------------------------
# The output from the "uname" command is used for this. Although not all of
# these are currently used, we store all fields in separate variables.
#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
# The options supported on uname from GNU coreutils 8.22:
#------------------------------------------------------------------------------
#   -a, --all                print all information, in the following order,
#                              except omit -p and -i if unknown:
#   -s, --kernel-name        print the kernel name
#   -n, --nodename           print the network node hostname
#   -r, --kernel-release     print the kernel release
#   -v, --kernel-version     print the kernel version
#   -m, --machine            print the machine hardware name
#   -p, --processor          print the processor type or "unknown"
#   -i, --hardware-platform  print the hardware platform or "unknown"
#   -o, --operating-system   print the operating system
#------------------------------------------------------------------------------
# Sample output:
# Linux ruudvan-vm-2-8-20200701 4.14.35-2025.400.8.el7uek.x86_64 #2 SMP Wed Aug 26 12:22:05 PDT 2020 x86_64 x86_64 x86_64 GNU/Linux
#------------------------------------------------------------------------------
  my $subr_name = get_my_name ();

  my $error_code;
  my $hostname_current;
  my $ignore_output;
  my $msg;
  my $target_cmd;
#------------------------------------------------------------------------------
# Test once if the command succeeds.  This avoids we need to check every
# specific # command below.
#------------------------------------------------------------------------------
  $target_cmd    = $g_mapped_cmds{uname};
  ($error_code, $ignore_output) = execute_system_cmd ($target_cmd);

  if ($error_code != 0)
#------------------------------------------------------------------------------
# This is unlikely to happen, but you never know.
#------------------------------------------------------------------------------
    {
      gp_message ("abort", $subr_name, "failure to execute the uname command");
    }
 
  my $kernel_name       = qx ($target_cmd -s); chomp ($kernel_name);
  my $nodename          = qx ($target_cmd -n); chomp ($nodename);
  my $kernel_release    = qx ($target_cmd -r); chomp ($kernel_release);
  my $kernel_version    = qx ($target_cmd -v); chomp ($kernel_version);
  my $machine           = qx ($target_cmd -m); chomp ($machine);
  my $processor         = qx ($target_cmd -p); chomp ($processor);
  my $hardware_platform = qx ($target_cmd -i); chomp ($hardware_platform);
  my $operating_system  = qx ($target_cmd -o); chomp ($operating_system);
 
  $local_system_config{"kernel_name"}       = $kernel_name;
  $local_system_config{"nodename"}          = $nodename;
  $local_system_config{"kernel_release"}    = $kernel_release;
  $local_system_config{"kernel_version"}    = $kernel_version;
  $local_system_config{"machine"}           = $machine;
  $local_system_config{"processor"}         = $processor;
  $local_system_config{"hardware_platform"} = $hardware_platform;
  $local_system_config{"operating_system"}  = $operating_system;
 
  gp_message ("debug", $subr_name, "the output from the $target_cmd command is split into the following variables:");
  gp_message ("debug", $subr_name, "kernel_name       = $kernel_name");
  gp_message ("debug", $subr_name, "nodename          = $nodename");
  gp_message ("debug", $subr_name, "kernel_release    = $kernel_release");
  gp_message ("debug", $subr_name, "kernel_version    = $kernel_version");
  gp_message ("debug", $subr_name, "machine           = $machine");
  gp_message ("debug", $subr_name, "processor         = $processor");
  gp_message ("debug", $subr_name, "hardware_platform = $hardware_platform");
  gp_message ("debug", $subr_name, "operating_system  = $operating_system");
 
#------------------------------------------------------------------------------
# Check if the system we are running on is supported.
#------------------------------------------------------------------------------
  my $is_supported = ${ check_support_for_processor (\$machine) };

  if (not $is_supported)
    {
      $msg = "the $machine instruction set architecture is not supported";
      gp_message ("error", $subr_name, $msg);
      gp_message ("diag", $subr_name, "Error: " . $msg);

      $msg = "temporarily ignored for development purposes";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
      exit (0);
    }
#------------------------------------------------------------------------------
# The current hostname is used to compare against the hostname(s) found in the
# experiment directories.
#------------------------------------------------------------------------------
  $target_cmd       = $g_mapped_cmds{hostname};
  $hostname_current = qx ($target_cmd); chomp ($hostname_current);
  $error_code       = ${^CHILD_ERROR_NATIVE};
 
  if ($error_code == 0)
    {
      $local_system_config{"hostname_current"} = $hostname_current;
    }
  else
#------------------------------------------------------------------------------
# This is unlikely to happen, but you never know.
#------------------------------------------------------------------------------
    {
      gp_message ("abort", $subr_name, "failure to execute the hostname command");
    }
  for my $key (sort keys %local_system_config)
    {
      gp_message ("debug", $subr_name, "local_system_config{$key} = $local_system_config{$key}");
    }

  return (0);

} #-- End of subroutine get_system_config_info

#------------------------------------------------------------------------------
# This subroutine prints a message.  Several types of messages are supported.
# In case the type is "abort", or "error", execution is terminated.
#
# Note that "debug", "warning", and "error" mode, the name of the calling
# subroutine is truncated to 30 characters.  In case the name is longer,
# a warning message # is issued so you know this has happened.
#
# Note that we use lcfirst () and ucfirst () to enforce whether the first
# character is printed in lower or uppercase.  It is nothing else than a
# convenience, but creates more consistency across messages.
#------------------------------------------------------------------------------
sub gp_message
{
  my $subr_name = get_my_name ();

  my ($action, $caller_name, $comment_line) = @_;

#------------------------------------------------------------------------------
# The debugXL identifier is special.  It is accepted, but otherwise ignored.
# This allows to (temporarily) disable debug print statements, but keep them
# around.
#------------------------------------------------------------------------------
  my %supported_identifiers = (
    "verbose"   => "[Verbose]",
    "debug"     => "[Debug]",
    "error"     => "[Error]",
    "warning"   => "[Warning]",
    "abort"     => "[Abort]",
    "assertion" => "[Assertion error]",
    "diag"      => "",
  );

  my $debug_size;
  my $identifier;
  my $fixed_size_name;
  my $ignore_value;
  my $string_limit = 30;
  my $strlen = length ($caller_name);
  my $trigger_debug = $FALSE;
  my $truncated_name;
  my $msg;

  if ($action =~ /debug\s*(.+)/)
    {
      if (defined ($1))
        {
          my $orig_value = $1;
          $debug_size = lc ($1);

          if ($debug_size =~ /^s$|^m$|^l$|^xl$/)
            {
              if ($g_debug_size{$debug_size})
                {
#------------------------------------------------------------------------------
# All we need to know is whether a debug action is requested and whether the
# size has been enabled.  By setting $action to "debug", the code below is
# simplified.  Note that only using $trigger_debug below is actually sufficient.
#------------------------------------------------------------------------------
                  $trigger_debug = $TRUE;
                }
            }
          else
            {
              die "$subr_name: debug size $orig_value is not supported";
            }
          $action = "debug";
        }
    }
  elsif ($action eq "debug")
    {
      $trigger_debug = $TRUE;
    }

#------------------------------------------------------------------------------
# Catch any non-supported identifier.
#------------------------------------------------------------------------------
  if (defined ($supported_identifiers{$action}))
    {
      $identifier = $supported_identifiers{$action};
    }
  else
    {
      die ("$subr_name - input error: $action is not supported");
    }
  if (($action eq "debug") and (not $g_debug))
    {
      $trigger_debug = $FALSE;
    }

#------------------------------------------------------------------------------
# Unconditionally buffer all warning messages.  These are available through the
# index.html page and cannot be disabled.
#
# If the quiet mode has been enabled, warnings are not printed though.
#------------------------------------------------------------------------------
  if ($action eq "warning")
    {
#------------------------------------------------------------------------------
# Remove any leading <br>, capitalize the first letter, and put the <br> back
# before storing the message in the buffer.
#------------------------------------------------------------------------------
      if ($comment_line =~ /^$g_html_new_line/)
        {
          $msg = $comment_line;
          $msg =~ s/$g_html_new_line//;
          $comment_line = $g_html_new_line . ucfirst ($msg);

          push (@g_warning_msgs, $comment_line);
        }
      else
        {
          push (@g_warning_msgs, ucfirst ($comment_line));
        }
    }

#------------------------------------------------------------------------------
# Unconditionally buffer all errror messages.  These will be printed prior to
# terminate execution.
#------------------------------------------------------------------------------
  if ($action eq "error")
#------------------------------------------------------------------------------
# Remove any leading <br>, capitalize the first letter, and put the <br> back.
#------------------------------------------------------------------------------
    {
      if ($comment_line =~ /^$g_html_new_line/)
        {
          $msg = $comment_line;
          $msg =~ s/$g_html_new_line//;
          $comment_line = $g_html_new_line . ucfirst ($msg);

          push (@g_error_msgs, $comment_line);
        }
      else
        {
          push (@g_error_msgs, ucfirst ($comment_line));
        }
    }

#------------------------------------------------------------------------------
# Quick return in several cases.  Note that "debug", "verbose", "warning", and
# "diag" messages are suppressed in quiet mode, but "error", "abort" and
# "assertion" always pass.
#------------------------------------------------------------------------------
  if ((
           ($action eq "verbose") and (not $g_verbose))
       or (($action eq "debug")   and (not $trigger_debug))
       or (($action eq "verbose") and ($g_quiet))
       or (($action eq "debug")   and ($g_quiet))
       or (($action eq "warning") and ($g_quiet))
       or (($action eq "diag")    and ($g_quiet)))
    {
      return (0);
    }

#------------------------------------------------------------------------------
# In diag mode, just print the input line and nothing else.
#------------------------------------------------------------------------------
  if ((
          $action eq "debug")
      or ($action eq "abort")
      or ($action eq "assertion"))
##      or ($action eq "error"))
    {
#------------------------------------------------------------------------------
# Construct the string to be printed.  Include an identifier and the name of
# the function.
#------------------------------------------------------------------------------
      if ($strlen > $string_limit)
        {
          $truncated_name  = substr ($caller_name, 0, $string_limit);
          $fixed_size_name = sprintf ("%-"."$string_limit"."s", $truncated_name);
          print "Warning in $subr_name - the name of the caller is: " .
		$caller_name . "\n";
          print "Warning in $subr_name - the string length is $strlen and " .
                "exceeds $string_limit\n";
        }
      else
        {
          $fixed_size_name = sprintf ("%-"."$string_limit"."s", $caller_name);
        }

##      if (($action eq "error") or ($action eq "abort"))
      if ($action eq "abort")
#------------------------------------------------------------------------------
# Enforce that the message starts with a lowercase symbol.  Since these are
# user errors, the name of the routine is not shown.  The same for "abort".
# If you want to display the routine name too, use an assertion.
#------------------------------------------------------------------------------
        {
          my $error_identifier = $supported_identifiers{"error"};
          if (@g_error_msgs)
            {
              $ignore_value = print_errors_buffer (\$error_identifier);
            }
          printf ("%-9s %s", $identifier, ucfirst ($comment_line));
          printf (" - %s\n", "execution is terminated");
        }
      elsif ($action eq "assertion")
#------------------------------------------------------------------------------
# Enforce that the message starts with a lowercase symbol.
#------------------------------------------------------------------------------
        {
#------------------------------------------------------------------------------
# The lines are too long, but breaking the argument list gives this warning:
# printf (...) interpreted as function
#------------------------------------------------------------------------------
          printf ("%-17s %-30s", $identifier, $fixed_size_name);
          printf (" - %s\n", $comment_line);
        }
      elsif (($action eq "debug") and ($trigger_debug))
#------------------------------------------------------------------------------
# Debug messages are printed "as is".  Avoids issues when searching for them ;-)
#------------------------------------------------------------------------------
        {
          printf ("%-9s %-30s", $identifier, $fixed_size_name);
          printf (" - %s\n", $comment_line);
        }
      else
#------------------------------------------------------------------------------
# Enforce that the message starts with a lowercase symbol.
#------------------------------------------------------------------------------
        {
          printf ("%-9s %-30s", $identifier, $fixed_size_name);
          printf (" - %s\n", $comment_line);
        }
    }
  elsif ($action eq "verbose")
#------------------------------------------------------------------------------
# The first character in the verbose message is capatilized.
#------------------------------------------------------------------------------
    {
      printf ("%s\n", ucfirst ($comment_line));
    }
  elsif ($action eq "diag")
#------------------------------------------------------------------------------
# The diag messages are meant to be diagnostics.  Only the comment line is
# printed.
#------------------------------------------------------------------------------
    {
      printf ("%s\n", $comment_line);
      return (0);
    }

#------------------------------------------------------------------------------
# Terminate execution in case the identifier is "abort".
#------------------------------------------------------------------------------
  if (($action eq "abort") or ($action eq "assertion"))
    {
##      print "ABORT temporarily disabled for testing purposes\n";
      exit (-1);
    }
  else
    {
      return (0);
    }
 
} #-- End of subroutine gp_message

#------------------------------------------------------------------------------
# Create an HTML page with the warnings.  If there are no warnings, include
# line to this extent.  The alternative is to supporess the entire page, but
# that breaks the consistency in the output.
#------------------------------------------------------------------------------
sub html_create_warnings_page
{
  my $subr_name = get_my_name ();

  my ($outputdir_ref) = @_;

  my $outputdir = ${ $outputdir_ref };

  my $file_title;
  my $html_acknowledgement;
  my $html_end;
  my $html_header;
  my $html_home_left;
  my $html_home_right;
  my $html_title_header;
  my $msg_no_warnings = "There are no warning messages issued.";
  my $page_title;
  my $position_text;
  my $size_text;

  my $outfile = $outputdir . $g_html_base_file_name{"warnings"} . ".html";

  gp_message ("debug", $subr_name, "outfile = $outfile");

  open (WARNINGS_OUT, ">", $outfile)
    or die ("unable to open $outfile for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile for writing");

  gp_message ("debug", $subr_name, "building warning file $outfile");

#------------------------------------------------------------------------------
# Generate some of the structures used in the HTML output.
#------------------------------------------------------------------------------
  $file_title  = "Warning messages";
  $html_header = ${ create_html_header (\$file_title) };
  $html_home_right   = ${ generate_home_link ("right") };

  $page_title    = "Warning Messages";
  $size_text     = "h2";
  $position_text = "center";
  $html_title_header = ${ generate_a_header (\$page_title, \$size_text, \$position_text) };

#------------------------------------------------------------------------------
# Get the acknowledgement, return to main link, and final html statements.
#------------------------------------------------------------------------------
  $html_home_left       = ${ generate_home_link ("left") };
  $html_acknowledgement = ${ create_html_credits () };
  $html_end             = ${ terminate_html_document () };

#------------------------------------------------------------------------------
# Generate the HTML file.
#------------------------------------------------------------------------------
  print WARNINGS_OUT $html_header;
  print WARNINGS_OUT $html_home_right;
  print WARNINGS_OUT $html_title_header;

  if ($g_total_warning_count > 0)
    {
      print WARNINGS_OUT "<pre>\n";
      print WARNINGS_OUT "$_\n" for @g_warning_msgs;
      print WARNINGS_OUT "</pre>\n";
    }
  else
    {
      print WARNINGS_OUT $msg_no_warnings;
    }

  print WARNINGS_OUT $html_home_left;
  print WARNINGS_OUT "<br>\n";
  print WARNINGS_OUT $html_acknowledgement;
  print WARNINGS_OUT $html_end;

  close (WARNINGS_OUT);

  return (0);

} #-- End of subroutine html_create_warnings_page

#------------------------------------------------------------------------------
# Generate the HTML with the experiment summary.
#------------------------------------------------------------------------------
sub html_generate_exp_summary
{
  my $subr_name = get_my_name ();

  my ($outputdir_ref, $experiment_data_ref) = @_;

  my $outputdir       = ${ $outputdir_ref };
  my @experiment_data = @{ $experiment_data_ref };
  my $file_title;
  my $outfile;
  my $page_title;
  my $size_text;
  my $position_text;
  my $html_header;
  my $html_home;
  my $html_title_header;
  my $html_acknowledgement;
  my $html_end;
  my @html_exp_table_data = ();
  my $html_exp_table_data_ref;
  my @table_execution_stats = ();
  my $table_execution_stats_ref;

  gp_message ("debug", $subr_name, "outputdir = $outputdir");
  $outputdir = append_forward_slash ($outputdir);
  gp_message ("debug", $subr_name, "outputdir = $outputdir");

  $file_title = "Experiment information";
  $page_title = "Experiment Information";
  $size_text = "h2";
  $position_text = "center";
  $html_header = ${ create_html_header (\$file_title) };
  $html_home   = ${ generate_home_link ("right") };

  $html_title_header = ${ generate_a_header (\$page_title, \$size_text, \$position_text) };

  $outfile = $outputdir . $g_html_base_file_name{"experiment_info"} . ".html";
  open (EXP_INFO, ">", $outfile)
    or die ("unable to open $outfile for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile for writing");

  print EXP_INFO $html_header;
  print EXP_INFO $html_home;
  print EXP_INFO $html_title_header;

  ($html_exp_table_data_ref, $table_execution_stats_ref) = html_generate_table_data ($experiment_data_ref);

  @html_exp_table_data   = @{ $html_exp_table_data_ref };
  @table_execution_stats = @{ $table_execution_stats_ref };

  print EXP_INFO "$_" for @html_exp_table_data;
;
##  print EXP_INFO "<pre>\n";
##  print EXP_INFO "$_\n" for @html_caller_callee;
##  print EXP_INFO "</pre>\n";

#------------------------------------------------------------------------------
# Get the acknowledgement, return to main link, and final html statements.
#------------------------------------------------------------------------------
  $html_home            = ${ generate_home_link ("left") };
  $html_acknowledgement = ${ create_html_credits () };
  $html_end             = ${ terminate_html_document () };

  print EXP_INFO $html_home;
  print EXP_INFO "<br>\n";
  print EXP_INFO $html_acknowledgement;
  print EXP_INFO $html_end;

  close (EXP_INFO);

  return (\@table_execution_stats);

} #-- End of subroutine html_generate_exp_summary

#------------------------------------------------------------------------------
# Generate the index.html file.
#------------------------------------------------------------------------------
sub html_generate_index
{
  my $subr_name = get_my_name ();

  my ($outputdir_ref, $html_first_metric_file_ref, $summary_metrics_ref,
      $number_of_metrics_ref, $function_info_ref, $function_address_info_ref,
      $sort_fields_ref, $exp_dir_list_ref, $addressobjtextm_ref,
      $metric_description_reversed_ref, $table_execution_stats_ref) = @_;

  my $outputdir               = ${ $outputdir_ref };
  my $html_first_metric_file  = ${ $html_first_metric_file_ref };
  my $summary_metrics         = ${ $summary_metrics_ref };
  my $number_of_metrics       = ${ $number_of_metrics_ref };
  my @function_info           = @{ $function_info_ref };
  my %function_address_info   = %{ $function_address_info_ref };
  my @sort_fields             = @{ $sort_fields_ref };
  my @exp_dir_list            = @{ $exp_dir_list_ref };
  my %addressobjtextm         = %{ $addressobjtextm_ref };
  my %metric_description_reversed = %{ $metric_description_reversed_ref };
  my @table_execution_stats   = @{ $table_execution_stats_ref };

  my @file_contents = ();

  my $acknowledgement;
  my @abs_path_exp_dirs = ();
  my $input_experiments;
  my $target_function;
  my $html_line;
  my $ftag;
  my $max_length = 0;
  my %html_source_functions = ();
  my $html_header;
  my @experiment_directories = ();
  my $html_acknowledgement;
  my $html_file_title;
  my $html_output_file;
  my $html_function_view;
  my $html_caller_callee_view;
  my $html_experiment_info;
  my $html_warnings_page;
  my $href_link;
  my $file_title;
  my $html_gprofng;
  my $html_end;
  my $max_length_metrics;
  my $page_title;
  my $size_text;
  my $position_text;

  my $ln;
  my $base;
  my $base_index_page;
  my $infile;
  my $outfile;
  my $rec;
  my $skip;
  my $callsize;
  my $dest;
  my $final_string;
  my @headers;
  my $header;
  my $sort_index;
  my $pc_address;
  my $anchor;
  my $directory_name;
  my $f2;
  my $f3;
  my $file;
  my $sline;
  my $src;
  my $srcfile_name;
  my $tmp1;
  my $tmp2;
  my $fullsize;
  my $regf2;
  my $trimsize;
  my $EIL;
  my $EEIL;
  my $AOBJ;
  my $RI;
  my $HDR;
  my $CALLER_CALLEE;
  my $NAME;
  my $SRC;
  my $TRIMMED;

#------------------------------------------------------------------------------
# Add a forward slash to make it easier when creating file names.
#------------------------------------------------------------------------------
  $outputdir         = append_forward_slash ($outputdir);
  gp_message ("debug", $subr_name, "outputdir = $outputdir");

  my $LANG              = $g_locale_settings{"LANG"};
  my $decimal_separator = $g_locale_settings{"decimal_separator"};

  $input_experiments = join (", ", @exp_dir_list);

  for my $i (0 .. $#exp_dir_list)
    {
      my $dir = get_basename ($exp_dir_list[$i]);
      push @abs_path_exp_dirs, $dir;
    }
  $input_experiments = join (", ", @abs_path_exp_dirs);

  gp_message ("debug", $subr_name, "input_experiments = $input_experiments");
 
#------------------------------------------------------------------------------
# TBD: Pass in the values for $expr_name and $cmd
#------------------------------------------------------------------------------
  $html_file_title = "Main index page";

  @experiment_directories = split (",", $input_experiments);
  $html_acknowledgement = ${ create_html_credits () };

  $html_end              = ${ terminate_html_document () };

  $html_output_file = $outputdir . $g_html_base_file_name{"index"} . ".html";

  open (INDEX, ">", $html_output_file)
    or die ("$subr_name - unable to open file $html_output_file for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $html_output_file for writing");

  $page_title    = "GPROFNG Performance Analysis";
  $size_text     = "h1";
  $position_text = "center";
  $html_gprofng = ${ generate_a_header (\$page_title, \$size_text, \$position_text) };

  $html_header     = ${ create_html_header (\$html_file_title) };

  print INDEX $html_header;
  print INDEX $html_gprofng;
  print INDEX "$_" for @g_html_experiment_stats;
  print INDEX "$_" for @table_execution_stats;

  $html_experiment_info  = "<a href=\'";
  $html_experiment_info .= $g_html_base_file_name{"experiment_info"} . ".html";
  $html_experiment_info .= "\'><h3>Experiment Details</h3></a>\n";

  $html_warnings_page  = "<a href=\'";
  $html_warnings_page .= $g_html_base_file_name{"warnings"} . ".html";
  $html_warnings_page .= "\'><h3>Warnings (" . $g_total_warning_count;
  $html_warnings_page .= ")</h3></a>\n";

  $html_function_view  = "<a href=\'";
  $html_function_view .= $html_first_metric_file;
  $html_function_view .= "\'><h3>Function View</h3></a>\n";

  $html_caller_callee_view  = "<a href=\'";
  $html_caller_callee_view .= $g_html_base_file_name{"caller_callee"} . ".html";
  $html_caller_callee_view .= "\'><h3>Caller Callee View</h3></a>\n";

  print INDEX "<br>\n";
##  print INDEX "<b>\n";
  print INDEX $html_experiment_info;
  print INDEX $html_warnings_page;
##  print INDEX "<br>\n";
##  print INDEX "<br>\n";
  print INDEX $html_function_view;
##  print INDEX "<br>\n";
##  print INDEX "<br>\n";
  print INDEX $html_caller_callee_view;
##  print INDEX "</b>\n";
##  print INDEX "<br>\n";
##  print INDEX "<br>\n";

  print INDEX $html_acknowledgement;
  print INDEX $html_end;

  close (INDEX);

  gp_message ("debug", $subr_name, "closed file $html_output_file");

  return (0);

} #-- End of subroutine html_generate_index

#------------------------------------------------------------------------------
# Generate the entries for the tables with the experiment info.
#------------------------------------------------------------------------------
sub html_generate_table_data
{
  my $subr_name = get_my_name ();

  my ($experiment_data_ref) = @_;

  my @experiment_data     = ();
  my @html_exp_table_data = ();
  my $html_line;
##  my $html_header_line;
  my $entry_name;
  my $key;
  my $size_text;
  my $position_text;
  my $title_table_1;
  my $title_table_2;
  my $title_table_3;
  my $title_table_summary;
  my $html_table_title;

  my @experiment_table_1_def = ();
  my @experiment_table_2_def = ();
  my @experiment_table_3_def = ();
  my @exp_table_summary_def = ();
  my @experiment_table_1 = ();
  my @experiment_table_2 = ();
  my @experiment_table_3 = ();
  my @exp_table_summary = ();
  my @exp_table_selection = ();

  @experiment_data = @{ $experiment_data_ref };

  for my $i (sort keys @experiment_data)
    {
      for my $fields (sort keys %{ $experiment_data[$i] })
        {
          gp_message ("debugXL", $subr_name, "$i => experiment_data[$i]{$fields} = $experiment_data[$i]{$fields}");
        }
    }

  $title_table_1 = "Target System Configuration";
  $title_table_2 = "Experiment Statistics";
  $title_table_3 = "Run Time Statistics";
  $title_table_summary = "Main Statistics";

  $size_text     = "h3";
  $position_text = "left";

  push @experiment_table_1_def, { name => "Experiment name" , key => "exp_name_short"};
  push @experiment_table_1_def, { name => "Hostname"        , key => "hostname"};
  push @experiment_table_1_def, { name => "Operating system", key => "OS"};
  push @experiment_table_1_def, { name => "Architecture",     key => "architecture"};
  push @experiment_table_1_def, { name => "Page size",        key => "page_size"};

  push @experiment_table_2_def, { name => "Target command"          , key => "target_cmd"};
  push @experiment_table_2_def, { name => "Date command executed"   , key => "start_date"};
  push @experiment_table_2_def, { name => "Data collection duration", key => "data_collection_duration"};
  push @experiment_table_2_def, { name => "End time of the experiment", key => "end_experiment"};

  push @experiment_table_3_def, { name => "User CPU time (seconds)", key => "user_cpu_time"};
##  push @experiment_table_3_def, { name => "User CPU time (percentage)", key => "user_cpu_percentage"};
  push @experiment_table_3_def, { name => "System CPU time (seconds)", key => "system_cpu_time"};
##  push @experiment_table_3_def, { name => "System CPU time (percentage)", key => "system_cpu_percentage"};
  push @experiment_table_3_def, { name => "Sleep time (seconds)", key => "sleep_time"};
##  push @experiment_table_3_def, { name => "Sleep time (percentage)", key => "sleep_percentage"};

  push @exp_table_summary_def, { name => "Experiment name" , key => "exp_name_short"};
  push @exp_table_summary_def, { name => "Hostname"        , key => "hostname"};
  push @exp_table_summary_def, { name => "User CPU time (seconds)", key => "user_cpu_time"};
  push @exp_table_summary_def, { name => "System CPU time (seconds)", key => "system_cpu_time"};
  push @exp_table_summary_def, { name => "Sleep time (seconds)", key => "sleep_time"};

  $html_table_title = ${ generate_a_header (\$title_table_1, \$size_text, \$position_text) };

  push (@html_exp_table_data, $html_table_title);

  @experiment_table_1 = @{ create_table (\@experiment_data, \@experiment_table_1_def) };

  push (@html_exp_table_data, @experiment_table_1);

  $html_table_title = ${ generate_a_header (\$title_table_2, \$size_text, \$position_text) };

  push (@html_exp_table_data, $html_table_title);

  @experiment_table_2 = @{ create_table (\@experiment_data, \@experiment_table_2_def) };

  push (@html_exp_table_data, @experiment_table_2);

  $html_table_title = ${ generate_a_header (\$title_table_3, \$size_text, \$position_text) };

  push (@html_exp_table_data, $html_table_title);

  @experiment_table_3 = @{ create_table (\@experiment_data, \@experiment_table_3_def) };

  push (@html_exp_table_data, @experiment_table_3);

  $html_table_title = ${ generate_a_header (\$title_table_summary, \$size_text, \$position_text) };

  push (@exp_table_summary, $html_table_title);

  @exp_table_selection = @{ create_table (\@experiment_data, \@exp_table_summary_def) };

  push (@exp_table_summary, @exp_table_selection);

  return (\@html_exp_table_data, \@exp_table_summary);

} #-- End of subroutine html_generate_table_data

#------------------------------------------------------------------------------
# Generate the HTML text to print in case a file is empty.
#------------------------------------------------------------------------------
sub html_text_empty_file
{
  my $subr_name = get_my_name ();

  my ($comment_ref, $error_file_ref) = @_;

  my $comment;
  my $error_file;
  my $error_message;
  my $file_title;
  my $html_end;
  my $html_header;
  my $html_home;

  my @html_empty_file = ();

  $comment     = ${ $comment_ref };
  $error_file  = ${ $error_file_ref };

  $file_title  = "File is empty";
  $html_header = ${ create_html_header (\$file_title) };
  $html_end    = ${ terminate_html_document () };
  $html_home   = ${ generate_home_link ("left") };

  push (@html_empty_file, $html_header);

  $error_message = "<b>" . $comment . "</b>";
  $error_message = set_background_color_string ($error_message, $g_html_color_scheme{"error_message"});
  push (@html_empty_file, $error_message);

  if (not is_file_empty ($error_file))
    {
      $error_message = "<p><em>Check file $error_file for more information</em></p>";
    }
  push (@html_empty_file, $error_message);
  push (@html_empty_file, $html_home);
  push (@html_empty_file, "<br>");
  push (@html_empty_file, $g_html_credits_line);
  push (@html_empty_file, $html_end);

  return (\@html_empty_file);

} #-- End of subroutine html_text_empty_file

#------------------------------------------------------------------------------
# This subroutine checks if a file is empty and returns $TRUE or $FALSE.
#------------------------------------------------------------------------------
sub is_file_empty
{
  my $subr_name = get_my_name ();

  my ($filename) = @_;

  my $is_empty;
  my $file_stat;
  my $msg;
  my $size;

  chomp ($filename);

  if (not -e $filename)
    {
#------------------------------------------------------------------------------
# The return value is used in the caller.  This is why we return the empty
# string in case the file does not exist.
#------------------------------------------------------------------------------
      $msg = "filename = $filename not found";
      gp_message ("debug", $subr_name, $msg);
      $is_empty = $TRUE;
    }
  else
    {
      $file_stat = stat ($filename);
      $size      = $file_stat->size;
      $is_empty  = ($size == 0) ? $TRUE : $FALSE;
    }

  $msg = "filename = $filename size = $size is_empty = $is_empty";
  gp_message ("debug", $subr_name, $msg);

  return ($is_empty);

} #-- End of subroutine is_file_empty

#------------------------------------------------------------------------------
# Check if a file is executable and return $TRUE or $FALSE.
#------------------------------------------------------------------------------
sub is_file_executable
{
  my $subr_name = get_my_name ();

  my ($filename) = @_;

  my $file_permissions;
  my $index_offset;
  my $is_executable;
  my $mode;
  my $number_of_bytes;
  my @permission_settings = ();
  my %permission_values = ();

  chomp ($filename);

  gp_message ("debug", $subr_name, "check if filename = $filename is executable");

  if (not -e $filename)
    {
#------------------------------------------------------------------------------
# The return value is used in the caller.  This is why we return the empty
# string in case the file does not exist.
#------------------------------------------------------------------------------
      gp_message ("debug", $subr_name, "filename = $filename not found");
      $is_executable = $FALSE;
    }
  else
    {
      $mode = stat ($filename)->mode;

      gp_message ("debugXL", $subr_name, "mode = $mode");
#------------------------------------------------------------------------------
# Get username.  We currently do not do anything with this though and the
# code is commented out.
#
#      my $my_name = getlogin () || getpwuid($<) || "Kilroy";
#      gp_message ("debug", $subr_name, "my_name = $my_name");
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Convert file permissions to octal, split the individual numbers and store
# the values for the respective users.
#------------------------------------------------------------------------------
      $file_permissions = sprintf("%o", $mode & 07777);

      @permission_settings = split (//, $file_permissions);

      $number_of_bytes = scalar (@permission_settings);

      gp_message ("debugXL", $subr_name, "file_permissions = $file_permissions");
      gp_message ("debugXL", $subr_name, "permission_settings = @permission_settings");
      gp_message ("debugXL", $subr_name, "number_of_settings = $number_of_bytes");

      if ($number_of_bytes == 4)
        {
          $index_offset = 1;
        }
      elsif ($number_of_bytes == 3)
        {
          $index_offset = 0;
        }
      else
        {
          my $msg = "unexpected number of $number_of_bytes bytes " .
                    "in permission settings: @permission_settings";
          gp_message ("assertion", $subr_name, $msg);
        }

      $permission_values{user}  = $permission_settings[$index_offset++];
      $permission_values{group} = $permission_settings[$index_offset++];
      $permission_values{other} = $permission_settings[$index_offset];

#------------------------------------------------------------------------------
# The executable bit should be set for user, group and other.  If this fails
# we mark the file as not executable.  Note that this is gprofng specific.
#------------------------------------------------------------------------------
      $is_executable = $TRUE;
      for my $k (keys %permission_values)
        {
          my $msg = "permission_values{" . $k . "} = " .
                    $permission_values{$k};
          gp_message ("debugXL", $subr_name, $msg);

          if ($permission_values{$k} % 2 == 0)
            {
              $is_executable = $FALSE;
              last;
            }
        }
    }

  gp_message ("debug", $subr_name, "is_executable = $is_executable");

  return ($is_executable);

} #-- End of subroutine is_file_executable

#------------------------------------------------------------------------------
# Print a message after a failure in $GP_DISPLAY_TEXT.
#------------------------------------------------------------------------------
sub msg_display_text_failure
{
  my $subr_name = get_my_name ();

  my ($gp_display_text_cmd, $error_code, $error_file) = @_;

  my $msg;

  $msg = "error code = $error_code - failure executing the following command:";
  gp_message ("error", $subr_name, $msg);

  gp_message ("error", $subr_name, $gp_display_text_cmd);

  $msg = "check file $error_file for more details";
  gp_message ("error", $subr_name, $msg);

  return (0);

} #-- End of subroutine msg_display_text_failure

#------------------------------------------------------------------------------
# TBD. Still needed? I think this entire function and usage can be removed.
#------------------------------------------------------------------------------
sub name_regex
{
  my $subr_name = get_my_name ();

  my ($metric_description_ref, $metrics, $field, $file) = @_;

  my %metric_description = %{ $metric_description_ref };

  my $msg;

  my @splitted_metrics;
  my $splitted_metrics;
  my $m;
  my $mf;
  my $nf;
  my $re = "This value should never show up anywhere";
  my $Xre;
#------------------------------------------------------------------------------
# Make sure to check for these to have a value.
#------------------------------------------------------------------------------
  my $noPCfile = undef;
  my $reported_metrics = undef;
  my @reported_metrics;
  my $hdr_regex;
  my $hdr_href_regex;
  my $hdr_src_regex;
  my $new_metrics;
  my $pre;
  my $post;
  my $rat;
  my @moo = ();

  my $gp_metrics_file;
  my $gp_metrics_dir;
  my $suffix_not_used;

  my $is_calls    = $FALSE;
  my $is_calltree = $FALSE;

  gp_message ("debugXL", $subr_name,"1:metrics->$metrics<- field->$field<- file->$file<-");

#------------------------------------------------------------------------------
# According to https://perldoc.perl.org/File::Basename, both dirname and
# basename are not reliable and fileparse () is recommended instead.
#
# Note that $gp_metrics_dir has a trailing "/".
#------------------------------------------------------------------------------
  ($gp_metrics_file, $gp_metrics_dir, $suffix_not_used) = fileparse ($file, ".sort.func-PC");

  gp_message ("debugXL", $subr_name, "gp_metrics_dir = $gp_metrics_dir gp_metrics_file = $gp_metrics_file");
  gp_message ("debugXL", $subr_name, "suffix_not_used = $suffix_not_used");

  if ($gp_metrics_file eq "calls")
    {
      $is_calls = $TRUE;
    }
  if ($gp_metrics_file eq "calltree")
    {
      $is_calltree = $TRUE;
    }

  $gp_metrics_file = "gp-metrics-" . $gp_metrics_file . "-PC";
  $gp_metrics_file = $gp_metrics_dir . $gp_metrics_file;

  gp_message ("debugXL", $subr_name, "gp_metrics_file is $gp_metrics_file");

  open (GP_METRICS, "<", $gp_metrics_file)
    or die ("$subr_name - unable to open gp_metrics file $gp_metrics_file for reading - '$!'");
  gp_message ("debug", $subr_name, "opened file $gp_metrics_file for reading");

  $new_metrics = $metrics;

  while (<GP_METRICS>)
    {
      $rat = $_;
      chomp ($rat);
      gp_message ("debugXL", $subr_name, "rat = $rat - new_metrics = $new_metrics");
#------------------------------------------------------------------------------
# Capture the string after "Current metrics:" and if it ends with ":name",
# remove it.
#------------------------------------------------------------------------------
      if ($rat =~ /^\s*Current metrics:\s*(.*)$/)
        {
          $new_metrics = $1;
          if ($new_metrics =~ /^(.*):name$/)
            {
              $new_metrics = $1;
            }
          last;
        }
    }
  close (GP_METRICS);

  if ($is_calls or $is_calltree)
    {
#------------------------------------------------------------------------------
# Remove any inclusive metrics from the list.
#------------------------------------------------------------------------------
      while ($new_metrics =~ /(.*)(i\.[^:]+)(.*)$/)
        {
          $pre  = $1;
          $post = $3;
          gp_message ("debugXL", $subr_name, "1b: new_metrics = $new_metrics pre = $pre post = $post");
          if (substr ($post,0,1) eq ":")
            {
              $post = substr ($post,1);
            }
          $new_metrics = $pre.$post;
        }
    }

  $metrics = $new_metrics;

  gp_message ("debugXL", $subr_name, "2:metrics->$metrics<- field->$field<- file->$file<-");

#------------------------------------------------------------------------------
# Find the line starting with "address:" and strip this part away.
#------------------------------------------------------------------------------
  if ($metrics =~ /^address:(.*)/)
    {
      $reported_metrics = $1;
#------------------------------------------------------------------------------
# Focus on the filename ending with "-PC".  When found, strip this part away.
#------------------------------------------------------------------------------
      if ($file =~ /^(.*)-PC$/)
        {
          $noPCfile = $1;
          if ($noPCfile =~ /^(.*)functions.sort.func$/)
            {
              $noPCfile = $1."functions.func";
            }
          push (@moo, "$reported_metrics\n");
        }
    }

#------------------------------------------------------------------------------
# Split the list into an array with the individual metrics.
#
# TBD: This should be done only once!
#------------------------------------------------------------------------------
  if (not defined($reported_metrics))
    {
      $msg = "reported_metrics is not defined";
      gp_message ("debug", $subr_name, $msg);
    }
  else
    {
      $msg = "reported_metrics = " . $reported_metrics;
      gp_message ("debug", $subr_name, $msg);

      @reported_metrics = split (":", $reported_metrics);
      for my $i (@reported_metrics)
        {
          gp_message ("debugXL", $subr_name, "reported_metrics = $i");
        }

      $hdr_regex      = "^\\s*";
      $hdr_href_regex = "^\\s*";
      $hdr_src_regex  = "^(\\s+|<i>\\s+)";
    
      for my $m (@reported_metrics)
        {

          my $description = ${ retrieve_metric_description (\$m, \%metric_description) };
          gp_message ("debugXL", $subr_name, "m = $m description = $description");
          if (substr ($m,0,1) eq "e")
            {
              push (@moo,"$m:$description\n");
              $hdr_regex .= "(Excl\\.\.*)";
              $hdr_href_regex .= "(<a.*>)(Excl\\.)(<\/a>)([^<]+)";
              $hdr_src_regex .= "(Excl\\.\.*)";
              next;
            }
          if (substr ($m,0,1) eq "i")
            {
              push (@moo,"$m:$description\n");
              $hdr_regex .= "(Incl\\.\.*)";
              $hdr_href_regex .= "(<a.*>)(Incl\\.)(<\/a>)([^<]+)";
              $hdr_src_regex .= "(Incl\\.\.*)";
              next;
            }
          if (substr ($m,0,1) eq "a")
            {
              my $a;
              my $am;
              $a = $m;
              $a =~ s/^a/e/;
              $am = ${ retrieve_metric_description (\$a, \%metric_description) };
              $am =~ s/Exclusive/Attributed/;
              push (@moo,"$m:$am\n");
              $hdr_regex .= "(Attr\\.\.*)";
              $hdr_href_regex .= "(<a.*>)(Attr\\.)(<\/a>)([^<]+)";
              $hdr_src_regex .= "(Attr\\.\.*)";next;
            }
        }
    }
    
      $hdr_regex      .= "(Name\.*)";
      $hdr_href_regex .= "(Name\.*)";

      @splitted_metrics = split (":","$metrics");
      $nf               = scalar (@splitted_metrics);
      gp_message ("debug", $subr_name,"number of fields in $metrics -> $nf");

      if (not defined($noPCfile))
        {
          $msg = "noPCfile is not defined";
          gp_message ("debug", $subr_name, $msg);
        }
      else
        {
          $msg = "noPCfile = " . $noPCfile;
          gp_message ("debug", $subr_name, $msg);

          open (ZMETRICS, ">", "$noPCfile.metrics")
            or die ("Not able to open file $noPCfile.metrics for writing - '$!'");
          gp_message ("debug", $subr_name, "$noPCfile - opened file $noPCfile.metrics for writing");
    
          print ZMETRICS @moo;
          close (ZMETRICS);

          gp_message ("debug", $subr_name, "wrote file $noPCfile.metrics");

          open (XREGEXP, ">", "$noPCfile.c.regex")
            or die ("Not able to open file $noPCfile.c.regex for writing - '$!'");
          gp_message ("debug", $subr_name, "$noPCfile - opened file $noPCfile.c.regex for writing");

          print XREGEXP "\# Number of metric fields\n";
          print XREGEXP "$nf\n";
          print XREGEXP "\# Header regex\n";
          print XREGEXP "$hdr_regex\n";
          print XREGEXP "\# href Header regex\n";
          print XREGEXP "$hdr_href_regex\n";
          print XREGEXP "\# src Header regex\n";
          print XREGEXP "$hdr_src_regex\n";

          $mf = 1;
#---------------------------------------------------------------------------
# Find the index of "field" in the metric list, plus one.
#---------------------------------------------------------------------------
          if ( ($field eq "functions") or ($field eq "calls") or ($field eq "calltree"))
            {
              $mf = $nf + 1;
            }
          else
            {
              for my $candidate_metric (@splitted_metrics)
                {
                  gp_message ("debugXL", $subr_name, "field = $field candidate_metric = $candidate_metric and mf = $mf");
                  if ($candidate_metric eq $field)
                    {
                      last;
                    }
                  $mf++;
                }
            }
          gp_message ("debugXL", $subr_name, "Final value mf = $mf");

          if ($mf == 1)
            {
              $re = "^\\s*(\\S+)"; # metric value
            }
          else
            {
              $re = "^\\s*\\S+";
            }
          $Xre = "^\\s*(\\S+)";

          $m = 2;
          while (--$nf)
            {
              if ($nf)
                {
                  if ($m == $mf)
                    {
                      $re .= "\\s+(\\S+)"; # metric value
                    }
                  else
                    {
                      $re .= "\\s+\\S+";
                    }
                  if ($nf != 1)
                    {
                      $Xre .= "\\s+(\\S+)";
                    }
                  $m++;
                }
            }

          if ($field eq "calltree")
            {
              $re .= "\\s+.*\\+-(.*)"; # name
              $Xre .= "\\s+.*\\+-(.*)\$"; # name (Right?)
            }
          else
            {
              $re .= "\\s+(.*)"; # name
              $Xre .= "\\s+(.*)\$"; # name
            }

          print XREGEXP "\# Metrics and Name regex\n";
          print XREGEXP "$Xre\n";
          close (XREGEXP);
        
          gp_message ("debug", $subr_name, "wrote file $noPCfile.c.regex");
          gp_message ("debugXL", $subr_name, "on return Xre = $Xre");
          gp_message ("debugXL", $subr_name, "on return re  = $re");
        }

  return ($re);

} #-- End of subroutine name_regex

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub nosrc
{
  my $subr_name = get_my_name ();

  my ($input_string) = @_;

  my $directory_name = append_forward_slash ($input_string);
  my $LANG           = $g_locale_settings{"LANG"};
  my $result_file    = $directory_name."no_source.html";

  gp_message ("debug", $subr_name, "result_file = $result_file");

  open (NS, ">", $result_file)
    or die ("$subr_name: cannot open file $result_file for writing - '$!'");

  print NS "<!doctype html public \"-//w3c//dtd html 3.2//en\">\n<html lang=\"$LANG\">\n<head>\n".
           "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n" .
           "<title>No source</title></head><body lang=\"$LANG\" bgcolor=".$g_html_color_scheme{"background_color_page"}."><pre>\n";
  print NS "<a name=\"line1\"></a><font color=#C80707>"."No source was found"."</font>\n"; # red font
  print NS "</pre>\n<pre>Output generated by $version_info</pre>\n";
  print NS "</body></html>\n";

  close (NS);

  return (0);

} #-- End of subroutine nosrc

#------------------------------------------------------------------------------
# TBD.
#------------------------------------------------------------------------------
sub numerically
{
  my $f1;
  my $f2;

  if ($a =~ /^([^\d]*)(\d+)/)
    {
      $f1 = int ($2);
      if ($b=~ /^([^\d]*)(\d+)/)
        {
          $f2 = int ($2);
          $f1 == $f2 ? 0 : ($f1 < $f2 ? -1 : +1);
        }
    }
  else
    {
      return ($a <=> $b);
    }
} #-- End of subroutine numerically

#------------------------------------------------------------------------------
# Parse the user options. Also perform a basic check.  More checks and also
# some more specific to the option, plus cross option checks,  will be
# performed soon after this subroutine has executed.
#
# Warnings, but also errors, are buffered.  In this way we can collect as many
# warnings and errors as possible, before bailing out in case of an error.
#------------------------------------------------------------------------------
sub parse_and_check_user_options
{
  my $subr_name = get_my_name ();

  my @exp_dir_list;

  my $arg;
  my $calltree_value;
  my $debug_value;
  my $default_metrics_value;
  my $func_limit_value;
  my $found_exp_dir = $FALSE;
  my $ignore_metrics_value;
  my $ignore_value;
  my $msg;
  my $outputdir_value;
  my $quiet_value;
  my $hp_value;
  my $valid;
  my $verbose_value;

  my $number_of_fields;

  my $internal_option_name;
  my $option_name;

  my $verbose = undef;
  my $warning = undef;

  my @opt_debug                = ();
  my @opt_highlight_percentage = ();
  my @opt_nowarnings           = ();
  my @opt_obsoleted_hp         = ();
  my @opt_output               = ();
  my @opt_overwrite            = ();
  my @opt_quiet                = ();
  my @opt_verbose              = ();
  my @opt_warnings             = ();

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
  my $no_of_warnings;
  my $total_warning_msgs = 0;
  my $option_value;
  my $option_warnings;
  my $no_of_warnings_ref;
  my $no_of_errors_ref;

  my $index_exp;
  my $first = $TRUE;
  my $trigger = $FALSE;
  my $found_non_exp = $FALSE;
  my $name_non_exp_dir;
  my $no_of_experiments = 0;

  my @opt_help = ();
  my @opt_version = ();
  my $stop_execution = $FALSE;

  my $option_value_ref;
  my $max_occurrences;
#------------------------------------------------------------------------------
# Configure Getopt to:
# - Silence warnings, since these are handled by the code.
# - Enforce case sensitivity in order to support -o and -O for example.
#------------------------------------------------------------------------------
  Getopt::Long::Configure("pass_through", "no_ignore_case");

#------------------------------------------------------------------------------
# Check for the --help and --version options.  Print a message and exit.
# Note that we support using both options simultaneously on the command line.
#------------------------------------------------------------------------------
  GetOptions (
    "help"    => \@opt_help,
    "version" => \@opt_version
  );

  if (@opt_help)
    {
      $stop_execution = $TRUE;
      $ignore_value   = print_help_info ();
    }
  if (@opt_version)
    {
      $stop_execution = $TRUE;
      $ignore_value   = print_version_info ();
    }

  if ($stop_execution)
    {
      exit (0);
    }

#------------------------------------------------------------------------------
# First, scan ARGV for the experiment names.  If there are no names, or the
# list with the names is not contiguous (meaning there is an non-experiment
# name in this list), an error message is printed and execution is terminated.
#
# Upon return from this function, the list with the experiment names is
# known and has been removed from ARGV.
#
# As a result, exp_dir_list is available from there on.
#
# This makes the subsequent processing of ARGV with GetOptions()  easier.
#------------------------------------------------------------------------------
  @exp_dir_list = @{ check_the_experiment_list () };

#------------------------------------------------------------------------------
# Configure Getopt to:
# - Silence warnings, since these are handled by the code.
# - Enforce case sensitivity in order to support -o and -O for example.
# - Allow unique abbreviations (also the default).
#------------------------------------------------------------------------------
  Getopt::Long::Configure("pass_through", "no_ignore_case", "auto_abbrev");
#------------------------------------------------------------------------------
# Get the remaining command line options.
#
# Recall:
# = => option requires a value
# : => option value is optional
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# All options are considered to be a string.
#
# We request every option supported to have an optional value.  Otherwise,
# GetOptions skips an option that does not have a value.
#
# The logic that parses the options deals with this and checks if an option
# that should have a value, actually has one.
#------------------------------------------------------------------------------
  GetOptions (
    "verbose|v:s"            => \@opt_verbose,
    "debug|d:s"              => \@opt_debug,
    "warnings|w:s"           => \@opt_warnings,
    "nowarnings:s"           => \@opt_nowarnings,
    "quiet|q:s"              => \@opt_quiet,
    "output|o=s"             => \@opt_output,
    "overwrite|O=s"          => \@opt_overwrite,
    "highlight-percentage=s" => \@opt_highlight_percentage,
    "hp=s"                   => \@opt_obsoleted_hp
  );

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Handle the user input and where needed, generate warnings.  In a later stage
# we check for (cross option) errors and warnings.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# The very first thing to do is to determine if the user has enabled one of the
# following options and take action accordingly:
# --quiet, --verbose, --debug, --warnings
#
# We first need to check for quiet mode to be set.  If so, all messages need to
# be silenced, regardless of the settings for verbose, debug, and warnings.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# The quiet option.
#------------------------------------------------------------------------------
  if (@opt_quiet)
    {
      $max_occurrences      = 1;
      $internal_option_name = "quiet";
      $option_name          = "--quiet";

      my ($valid_ref) = extract_option_value (\@opt_quiet,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);

      $valid = ${ $valid_ref };

      if ($valid)
        {
          $g_quiet = $g_user_settings{"quiet"}{"current_value"} eq "on" ?
								$TRUE : $FALSE;
        }
    }

#------------------------------------------------------------------------------
# The debug option.
#------------------------------------------------------------------------------
  if (@opt_debug)
    {
      $max_occurrences      = 1;
      $internal_option_name = "debug";
      $option_name          = "-d/--debug";

      my ($valid_ref) = extract_option_value (\@opt_debug,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);

      $valid = ${ $valid_ref };

      if ($valid)
#------------------------------------------------------------------------------
# Set the appropriate debug size (e.g. "XL") in a table that is used in the
# gp_message() subroutine.
#------------------------------------------------------------------------------
        {
          $g_debug = $TRUE;
          $ignore_value = set_debug_size ();
        }
    }

#------------------------------------------------------------------------------
# The verbose option.
#------------------------------------------------------------------------------
  if (@opt_verbose)
    {
      $max_occurrences      = 1;
      $internal_option_name = "verbose";
      $option_name          = "--verbose";

      my ($valid_ref) = extract_option_value (\@opt_verbose,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
      $valid = ${ $valid_ref };

      if ($valid)
        {
          $g_verbose = $g_user_settings{"verbose"}{"current_value"} eq "on" ?
								$TRUE : $FALSE;
        }
    }

#------------------------------------------------------------------------------
# The nowarnings option.
#------------------------------------------------------------------------------
  if (@opt_nowarnings)
    {
      $max_occurrences      = 1;
      $internal_option_name = "nowarnings";
      $option_name          = "--nowarnings";

      my ($valid_ref) = extract_option_value (\@opt_nowarnings,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);

      $valid = ${ $valid_ref };

      if ($valid)
        {
          $g_warnings =
		$g_user_settings{"nowarnings"}{"current_value"} eq "on" ?
								$FALSE : $TRUE;
        }
    }

#------------------------------------------------------------------------------
# The warnings option (deprecated).
#------------------------------------------------------------------------------
  if (@opt_warnings)
    {
      $max_occurrences      = 1;
      $internal_option_name = "warnings";
      $option_name          = "--warnings";

      my ($valid_ref) = extract_option_value (\@opt_warnings,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
    }

#------------------------------------------------------------------------------
# At this point, the debug, verbose, warnings and quiet settings are known.
# This subroutine makes the final decision on these settings.  For example, if
# quiet mode has been specified, the settings for debug, verbose and warnings
# are ignored.
#------------------------------------------------------------------------------
  $ignore_value = finalize_special_options ();

#------------------------------------------------------------------------------
# A this point we know we can start printing messages in case verbose and/or
# debug mode have been set.
#------------------------------------------------------------------------------
  $msg = "the original command line options: " . join (", ", @CopyOfARGV);
  gp_message ("debug", $subr_name, $msg);

  $msg = "the command line options after the special options: " .
         join (", ", @ARGV);
  gp_message ("debug", $subr_name, $msg);

  gp_message ("verbose", $subr_name, "Parsing the user options");

#------------------------------------------------------------------------------
# The output option.
#------------------------------------------------------------------------------
  if (@opt_output)
    {
      $max_occurrences      = 1;
      $internal_option_name = "output";
      $option_name          = "-o/--output";

      my ($valid_ref) = extract_option_value (\@opt_output,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
    }

#------------------------------------------------------------------------------
# The overwrite option.
#------------------------------------------------------------------------------
  if (@opt_overwrite)
    {
      $max_occurrences      = 1;
      $internal_option_name = "overwrite";
      $option_name          = "-O/--overwrite";

      my ($valid_ref) = extract_option_value (\@opt_overwrite,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
    }

#------------------------------------------------------------------------------
# The highlight-percentage option.
#------------------------------------------------------------------------------
  if (@opt_highlight_percentage)
    {
      $max_occurrences      = 1;
      $internal_option_name = "highlight_percentage";
      $option_name          = "--highlight-percentage";

      my ($valid_ref) = extract_option_value (\@opt_highlight_percentage,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
    }

#------------------------------------------------------------------------------
# The hp option (deprecated)
#------------------------------------------------------------------------------
  if (@opt_obsoleted_hp)
    {
      $max_occurrences      = 1;
      $internal_option_name = "hp";
      $option_name          = "-hp";

      my ($valid_ref) = extract_option_value (\@opt_obsoleted_hp,
					      \$max_occurrences,
					      \$internal_option_name,
					      \$option_name);
    }

#------------------------------------------------------------------------------
# By now, all options given on the command line have been processed and the
# list with experiment directories is known.
#
# Process the remainder of ARGV, but other than the option generated by the
# driver, ARGV should be empty.
#------------------------------------------------------------------------------
  $ignore_value = wrap_up_user_options ();

# Temporarily disabled       elsif (($arg eq "-fl") or ($arg eq "--func-limit"))
# Temporarily disabled       elsif (($arg eq "-ct") or ($arg eq "--calltree"))
# Temporarily disabled       elsif (($arg eq "-tp") or ($arg eq "--threshold-percentage"))
# Temporarily disabled       elsif (($arg eq "-dm") or ($arg eq "--default-metrics"))
# Temporarily disabled       elsif (($arg eq "-im") or ($arg eq "--ignore-metrics"))

  if (@exp_dir_list)
#------------------------------------------------------------------------------
# Print the list of the experiment directories found.
#
# Note that later we also check for these directories to actually exist
# and be valid experiments..
#------------------------------------------------------------------------------
    {
      $found_exp_dir = $TRUE;
      $msg = "the following experiment directories will be used:";
      gp_message ("debug", $subr_name, $msg);
      for my $i (keys @exp_dir_list)
        {
          my $msg = "exp_dir_list[$i] = $exp_dir_list[$i]";
          gp_message ("debug", $subr_name, $msg);
        }
    }
  else
#------------------------------------------------------------------------------
# Print a message if the experiment list is not valid, or empty.  There will
# also be error messages in the buffer. These will be printed later.
#------------------------------------------------------------------------------
    {
      $msg = "experiment directory name(s) are either not valid, or missing";
      gp_message ("debug", $subr_name, $msg);
    }

  return (\$found_exp_dir, \@exp_dir_list);

} #-- End of subroutine parse_and_check_user_options

#------------------------------------------------------------------------------
# Parse the generated .dis files
#------------------------------------------------------------------------------
sub parse_dis_files
{
  my $subr_name = get_my_name ();

  my ($number_of_metrics_ref, $function_info_ref,
      $function_address_and_index_ref, $input_string_ref,
      $addressobj_index_ref) = @_;

#------------------------------------------------------------------------------
# Note that $function_address_and_index_ref is not used, but we need to pass
# in the address into generate_dis_html.
#------------------------------------------------------------------------------
  my $number_of_metrics = ${ $number_of_metrics_ref };
  my @function_info     = @{ $function_info_ref };
  my $input_string      = ${ $input_string_ref };
  my %addressobj_index  = %{ $addressobj_index_ref };

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $dis_filename_id_regex = 'file\.([0-9]+)\.dis';

  my $filename;
  my $msg;
  my $outputdir = append_forward_slash ($input_string);

  my @source_line = ();
  my $source_line_ref;

  my @metric = ();
  my $metric_ref;

  my $target_function;

  gp_message ("debug", $subr_name, "building disassembly files");
  gp_message ("debug", $subr_name, "outputdir = $outputdir");

  while (glob ($outputdir.'*.dis'))
    {
      gp_message ("debug", $subr_name, "processing disassembly file: $_");

      my $base_name = get_basename ($_);

      if ($base_name =~ /$dis_filename_id_regex/)
        {
          if (defined ($1))
            {
              gp_message ("debug", $subr_name, "processing disassembly file: $base_name $1");
              if (exists ($function_info[$1]{"routine"}))
                {
                  $target_function = $function_info[$1]{"routine"};
                  gp_message ("debug", $subr_name, "processing disassembly file: $base_name target_function = $target_function");
                }
              if (exists ($g_function_tag_id{$target_function}))
                {
                  gp_message ("debug", $subr_name, "target_function = $target_function ftag = $g_function_tag_id{$target_function}");
                }
              else
                {
                  my $msg = "no function tag found for $target_function";
                  gp_message ("assertion", $subr_name, $msg);
                }
            }
          else
            {
              gp_message ("debug", $subr_name, "processing disassembly file: $base_name unknown id");
            }
        }
 
      $filename = $_;
      gp_message ("verbose", $subr_name, "  Processing disassembly file $filename");
      ($source_line_ref, $metric_ref) = generate_dis_html (
                                          \$target_function,
                                          \$number_of_metrics,
                                          $function_info_ref,
                                          $function_address_and_index_ref,
                                          \$outputdir,
                                          \$filename,
                                          \@source_line,
                                          \@metric,
                                          \%addressobj_index);

      @source_line = @{ $source_line_ref };

#------------------------------------------------------------------------------
# TBD.  This part needs work.  The return variables from generate_dis_html ()
# are not used, so the code below is meaningless, but awaiting a true fix,
# the problem which appears on aarch64 is bypassed.
#------------------------------------------------------------------------------
      if (defined ($metric_ref))
        {
          @metric = @{ $metric_ref };
        }
      else
        {
          $msg = "metric_ref after generate_dis_html is undefined";
          gp_message ("debug", $subr_name, $msg);
        }
    }

  return (0)

} #-- End of subroutine parse_dis_files

#------------------------------------------------------------------------------
# Parse the .src.txt files
#------------------------------------------------------------------------------
sub parse_source_files
{
  my $subr_name = get_my_name ();

  my ($number_of_metrics_ref, $function_info_ref, $outputdir_ref) = @_;

  my $number_of_metrics = ${ $number_of_metrics_ref };
  my $outputdir         = ${ $outputdir_ref };
  my $ignore_value;

  my $outputdir_with_slash = append_forward_slash ($outputdir);

  gp_message ("verbose", $subr_name, "building source files");

  while (glob ($outputdir_with_slash.'*.src.txt'))
    {
      gp_message ("verbose", $subr_name, "  Processing source file: $_");
      gp_message ("debug", $subr_name, "processing source file: $_");

      my $found_target = process_source (
                           $number_of_metrics,
                           $function_info_ref,
                           $outputdir_with_slash,
                           $_);

      if (not $found_target)
        {
          gp_message ("debug", $subr_name, "target function not found");
        }
    }

} #-- End of subroutine parse_source_files

#------------------------------------------------------------------------------
# Routine to prepend \\ to selected symbols.
#------------------------------------------------------------------------------
sub prepend_backslashes
{
  my $subr_name = get_my_name ();

  my ($target_string) = @_;

  gp_message ("debug", $subr_name, "target_string on entry  = $target_string");

  $target_string =~ s/\(/\\\(/g;
  $target_string =~ s/\)/\\\)/g;
  $target_string =~ s/\+/\\\+/g;
  $target_string =~ s/\[/\\\[/g;
  $target_string =~ s/\]/\\\]/g;
  $target_string =~ s/\*/\\\*/g;
  $target_string =~ s/\./\\\./g;
  $target_string =~ s/\$/\\\$/g;
  $target_string =~ s/\^/\\\^/g;
  $target_string =~ s/\#/\\\#/g;

  gp_message ("debug", $subr_name, "target_string on return = $target_string");

  return ($target_string);

} #-- End of subroutine prepend_backslashes

#------------------------------------------------------------------------------
# TBD Still needed?
#------------------------------------------------------------------------------
sub preprocess_function_files
{
  my $subr_name = get_my_name ();

  my ($metric_description_ref, $script_pc_metrics, $input_string, $sort_fields_ref) = @_;

  my $outputdir   = append_forward_slash ($input_string);
  my @sort_fields = @{ $sort_fields_ref };

  my $error_code;
  my $cmd_output;
  my $re;

# TBD  $outputdir .= "/";

  my %metric_description = %{ $metric_description_ref };

  for my $m (keys %metric_description)
    {
      gp_message ("debug", $subr_name, "metric_description{$m} = $metric_description{$m}");
    }

  $re = name_regex ($metric_description_ref, $script_pc_metrics, "functions", $outputdir."functions.sort.func-PC");
  ($error_code, $cmd_output) = execute_system_cmd ("echo '$re' > $outputdir"."functions.sort.func-PC.name-regex");
  if ($error_code != 0 )
    {
      gp_message ("abort", $subr_name, "execution terminated");
    }

  for my $field (@sort_fields)
    {
      $re = name_regex ($metric_description_ref, $script_pc_metrics, $field, $outputdir."$field.sort.func-PC");
      ($error_code, $cmd_output) = execute_system_cmd ("echo '$re' > $outputdir"."$field.sort.func-PC.name-regex");
      if ($error_code != 0 )
        {
          gp_message ("abort", $subr_name, "execution terminated");
        }
    }

  $re = name_regex ($metric_description_ref, $script_pc_metrics, "calls", $outputdir."calls.sort.func-PC");
  ($error_code, $cmd_output) = execute_system_cmd ("echo '$re' > $outputdir"."calls.sort.func-PC.name-regex");
  if ($error_code != 0 )
    {
      gp_message ("abort", $subr_name, "execution terminated");
    }

  if ($g_user_settings{"calltree"}{"current_value"} eq "on")
    {
      $re = name_regex ($metric_description_ref, $script_pc_metrics, "calltree", $outputdir."calltree.sort.func-PC");
      ($error_code, $cmd_output) = execute_system_cmd ("echo '$re' > $outputdir"."calltree.sort.func-PC.name-regex");
      if ($error_code != 0 )
        {
          gp_message ("abort", $subr_name, "execution terminated");
        }
    }

  return (0);

} #-- End of subroutine preprocess_function_files

#------------------------------------------------------------------------------
# Print the original list with the command line options.
#------------------------------------------------------------------------------
sub print_command_line_options
{
  my ($identifier_ref) = @_;

  my $identifier = ${ $identifier_ref };
  my $msg;

  $msg = "The command line options (shown for ease of reference): ";
  printf ("%-9s %s\n", $identifier, ucfirst ($msg));

  $msg = join (", ", @CopyOfARGV);
  printf ("%-9s %s\n", $identifier, $msg);

#  printf ("%-9s\n", $identifier);

  return (0);

} #-- End of subroutine print_command_line_options

#------------------------------------------------------------------------------
# Print all the errors messages in the buffer.
#------------------------------------------------------------------------------
sub print_errors_buffer
{
  my $subr_name = get_my_name ();

  my ($identifier_ref) = @_;

  my $ignore_value;
  my $msg;
  my $plural_or_single;
  my $identifier = ${ $identifier_ref };

  $plural_or_single = ($g_total_error_count > 1) ? "errors have" : "error has";

  if (@g_warning_msgs and $g_warnings)
#------------------------------------------------------------------------------
# Make sure that all warnings are printed in case of an error.  This is to
# avoid that warnings get lost in case the program terminates early.
#------------------------------------------------------------------------------
    {
      $ignore_value = print_warnings_buffer ();
    }

  if (not $g_options_printed)
#------------------------------------------------------------------------------
# The options are printed as part of the warnings, so only if the warnings are
# not printed, we need to print them in case of errors.
#------------------------------------------------------------------------------
    {
      $g_options_printed = $TRUE;
      $ignore_value =  print_command_line_options (\$identifier);
    }

  $msg  =  "a total of " . $g_total_error_count;
  $msg .=  " fatal " . $plural_or_single . " been detected:";
  printf ("%-9s %s\n", $identifier, ucfirst ($msg));

  for my $key (keys @g_error_msgs)
    {
      $msg = $g_error_msgs[$key];
      printf ("%-11s %s\n", $identifier, ucfirst ($msg));
    }

  return (0);

} #-- End of subroutine print_errors_buffer

#------------------------------------------------------------------------------
# Print the help overview
#------------------------------------------------------------------------------
sub print_help_info
{
  my $space = " ";

  printf("%s\n",
  "Usage: $driver_cmd [OPTION(S)] EXPERIMENT(S)");
  printf("\n");
  printf("%s\n",
  "Process one or more experiments to generate a directory containing the");
  printf("%s\n",
  "index.html file that may be used to browse the experiment data.");
  printf("\n");
  printf("%s\n",
  "Options:");
  printf("\n");
  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("--help",
  "Print usage information and exit.");

  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("--version",
  "Print the version number and exit.");

  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("--verbose",
  "Enable verbose mode to show diagnostic messages about the");
  print_help_line ("",
  "processing of the data.  By default verbose mode is disabled.");

  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("-d [<db-vol-size>], --debug[=<db-vol-size>]",
  "Control the printing of run time debug information to assist with");
  print_help_line ("",
  "the troubleshooting, or further development of this tool.");
  print_help_line ("",
  "The <db-vol-size> parameter controls the output volume and is");
  print_help_line ("",
  "one from the list {s | S | m | M | l | L | xl | XL}.");
  print_help_line ("",
  "If db-vol-size is not specified, a modest amount of information");
  print_help_line ("",
  "is printed.  This is equivalent to select size s, or S. The");
  print_help_line ("",
  "volume of data goes up as the size increases.  Note that");
  print_help_line ("",
  "currently l/L is  equivalent to xl/XL, but this is expected to");
  print_help_line ("",
  "change in future updates.  By default debug mode is disabled.");

  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("--highlight-percentage=<value>",
  "A percentage value in the interval [0,100] to select and color");
  print_help_line ("",
  "code source lines, as well as instructions, that are within this");
  print_help_line ("",
  "percentage of the maximum metric value(s).  A value of zero");
  print_help_line ("",
  "disables this feature.  The default value is 90 (%).");

  #-------Marker line - do not go beyond this line ----------------------------
  print_help_line ("-o <dirname>, --output=<dirname>",
  "Use <dirname> as the directory name to store the results in.");
  print_help_line ("",
  "In absence of this option, the default name is display.<n>.html.");
  print_help_line ("",
  "This directory is created in the current directory.  The number");
  print_help_line ("",
  "<n> is the first positive integer number not in use in this");
  print_help_line ("",
  "naming scheme.  An existing directory with the same name is not");
  print_help_line ("",
  "overwritten.  Make sure that umask is set to the correct access");
  print_help_line ("",
  "permissions.");

  #-------Marker line - do not go beyond this line --------------------------
  print_help_line ("-O <dirname>, --overwrite=<dirname>",
  "Use <dirname> as the directory name to store the results in.");
  print_help_line ("",
  "In absence of this option, the default name is display.<n>.html.");
  print_help_line ("",
  "This directory is created in the current directory.  The number");
  print_help_line ("",
  "<n> is the first positive integer number not in use in this");
  print_help_line ("",
  "naming scheme.  An existing directory with the same name is");
  print_help_line ("",
  "silently overwritten.  Make sure that umask is set to the");
  print_help_line ("",
  "correct access permissions.");

  #-------Marker line - do not go beyond this line --------------------------
  print_help_line ("-q, --quiet",
  "Disable the display of all warning, debug, verbose and any");
  print_help_line ("",
  "other messages.  If enabled, the settings for verbose and debug");
  print_help_line ("",
  "are accepted, but ignored.  With this option, there is no screen");
  print_help_line ("",
  "output, other than errors.  By default quiet mode is disabled");

  #-------Marker line - do not go beyond this line --------------------------
  print_help_line ("--nowarnings",
  "Disable the printing of warning messages on stdout.  By default");
  print_help_line ("",
  "warning messages are printed.");

  #-------Marker line - do not go beyond this line --------------------------
  printf("\n");
  printf ("%s\n","Report bugs to <https://sourceware.org/bugzilla/>");

  return (0);

} #-- End of subroutine print_help_info

#------------------------------------------------------------------------------
# Print a single line as part of the help output.
#
# If the first item is not the empty string, it is considered to be the
# option.  If the length of the option exceeds the limit set by $max_space,
# it is printed by itself and the text is printed on the next line.  Otherwise
# the text follows the option.
#
# To assist with the development of the help text, we check if the total length
# of the line exceeds the max numbers of columns (79 according to the GNU
# coding standards).
#------------------------------------------------------------------------------
sub print_help_line
{
  my $subr_name = get_my_name ();

  my ($item, $help_text) = @_;

  my $length_item = length ($item);
  my $max_col = 79;
  my $max_space = 14;
  my $no_of_spaces;
  my $pad;
  my $space = " ";
  my $the_message;

  if ($length_item > $max_col)
    {
      printf ("Error: $item is $length_item long - exceeds $max_col\n");
      exit (0);
    }
  elsif ( $length_item == 0 )
    {
      $no_of_spaces = $max_space;

      $pad = "";
      for my $i (1..$no_of_spaces)
        {
          $pad .= $space;
        }
      $the_message = $pad . $help_text;
    }
  else
    {
    if ($length_item < $max_space)
      {
        $no_of_spaces = $max_space - length ($item);
        $pad = "";
        for my $i (1..$no_of_spaces)
          {
            $pad .= $space;
          }
        $the_message = $item . $pad . $help_text;
      }
    else
      {
        $pad = "";
        for my $i (1..$max_space)
          {
            $pad .= $space;
          }
        printf("%s\n", $item);
        $the_message = $pad . $help_text;
      }
    }

  if (length ($the_message) <= $max_col)
    {
      printf ("%s\n", $the_message);
    }
  else
    {
      my $delta = length ($the_message) - $max_col;
      printf ("%s\n", "$the_message - exceeds $max_col by $delta");
      exit (0);
    }


  return (0);

} #-- End of subroutine print_help_line

#------------------------------------------------------------------------------
# Print the meta data for each experiment directory.
#------------------------------------------------------------------------------
sub print_meta_data_experiments
{
  my $subr_name = get_my_name ();

  my ($mode) = @_;

  for my $exp (sort keys %g_exp_dir_meta_data)
    {
      for my $meta (sort keys %{$g_exp_dir_meta_data{$exp}})
        {
          gp_message ($mode, $subr_name, "$exp => $meta = $g_exp_dir_meta_data{$exp}{$meta}");
        }
    }

  return (0);

} #-- End of subroutine print_meta_data_experiments

#------------------------------------------------------------------------------
# Brute force subroutine that prints the contents of a structure with function
# level information.  This version is for a top level array structure,
# followed by a hash.
#------------------------------------------------------------------------------
sub print_metric_function_array
{
  my $subr_name = get_my_name ();

  my ($metric, $struct_type_name, $target_structure_ref) = @_;

  my @target_structure = @{$target_structure_ref};

  gp_message ("debugXL", $subr_name, "contents of structure ".$struct_type_name."{".$metric."}:");

  for my $fields (sort keys @target_structure)
    {
          for my $elems (sort keys % {$target_structure[$fields]})
            {
              my $msg = $struct_type_name."{$metric}[$fields]{$elems} = ";
              $msg   .= $target_structure[$fields]{$elems};
              gp_message ("debugXL", $subr_name, $msg);
            }
    }

  return (0);

} #-- End of subroutine print_metric_function_array

#------------------------------------------------------------------------------
# Brute force subroutine that prints the contents of a structure with function
# level information.  This version is for a top level hash structure.  The
# next level may be another hash, or an array.
#------------------------------------------------------------------------------
sub print_metric_function_hash
{
  my $subr_name = get_my_name ();

  my ($sub_struct_type, $metric, $struct_type_name, $target_structure_ref) = @_;

  my %target_structure = %{$target_structure_ref};

  gp_message ("debugXL", $subr_name, "contents of structure ".$struct_type_name."{".$metric."}:");

  for my $fields (sort keys %target_structure)
    {
      gp_message ("debugXL", $subr_name, "metric = $metric fields = $fields");
      if ($sub_struct_type eq "hash_hash")
        {
          for my $elems (sort keys %{$target_structure{$fields}})
            {
              my $txt = $struct_type_name."{$metric}{$fields}{$elems} = ";
              $txt   .= $target_structure{$fields}{$elems};
              gp_message ("debugXL", $subr_name, $txt);
            }
        }
      elsif ($sub_struct_type eq "hash_array")
        {
          my $values = "";
          for my $elems (sort keys @{$target_structure{$fields}})
            {
              $values .= "$target_structure{$fields}[$elems] ";
            }
          gp_message ("debugXL", $subr_name, $struct_type_name."{$metric}{$fields} = $values");
        }
      else
        {
          my $msg = "sub-structure type '$sub_struct_type' is not supported";
          gp_message ("assertion", $subr_name, $msg);
        }
    }
 
  return (0);

} #-- End of subroutine print_metric_function_hash

#------------------------------------------------------------------------------
# Print the opening message.
#------------------------------------------------------------------------------
sub print_opening_message
{
  my $subr_name = get_my_name ();
#------------------------------------------------------------------------------
# Since the second argument is an array, we pass it in by reference.  The
# alternative is to make it the last argument.
#------------------------------------------------------------------------------
  my ($outputdir, $exp_dir_list_ref, $time_percentage_multiplier) = @_;

  my @exp_dir_list = @{$exp_dir_list_ref};

  my $msg;
  my $no_of_dirs = scalar (@exp_dir_list);
#------------------------------------------------------------------------------
# Build a comma separated list with all directory names.  If there is only one
# entry, the leading comma will not be inserted.
#------------------------------------------------------------------------------
  my $dir_list   = join (", ", @exp_dir_list);

#------------------------------------------------------------------------------
# If there are at least two entries, find the last comma and replace it by
# " and".  Note that we know there is at least one comma, so the value
# returned by rindex () cannot be -1.
#------------------------------------------------------------------------------
  if ($no_of_dirs > 1)
    {
      my $last_comma   = rindex ($dir_list, ",");
      my $ignore_value = substr ($dir_list, $last_comma, 1, " and");
    }
  $msg = "start $tool_name, generating directory $outputdir from $dir_list";

  gp_message ("verbose", $subr_name, $msg);

  if ($time_percentage_multiplier < 1.0)
    {
      $msg = "Handle at least ";
    }
  else
    {
      $msg = "Handle ";
    }

  $msg .= ($time_percentage_multiplier*100.0)."% of the time";
 
  gp_message ("verbose", $subr_name, $msg);

} #-- End of subroutine print_opening_message

#------------------------------------------------------------------------------
# TBD.
#------------------------------------------------------------------------------
sub print_program_header
{
  my $subr_name = get_my_name ();

  my ($mode, $tool_name, $binutils_version) = @_;

  my $header_limit = 60;
  my $dashes = "-";

#------------------------------------------------------------------------------
# Generate the dashed line
#------------------------------------------------------------------------------
  for (2 .. $header_limit)
    {
      $dashes .= "-";
    }

    gp_message ($mode, $subr_name, $dashes);
    gp_message ($mode, $subr_name, "Tool name: $tool_name");
    gp_message ($mode, $subr_name, "Version  : $binutils_version");
    gp_message ($mode, $subr_name, "Date     : " . localtime ());
    gp_message ($mode, $subr_name, $dashes);

} #-- End of subroutine print_program_header

#------------------------------------------------------------------------------
# Print a comment string, followed by the values of the options. The list
# with the keywords is sorted alphabetically.
#
# The value stored in $mode is passed on to gp_message ().  The intended use
# for this is to call this function in verbose and/or debug mode.
#
# The comment string is converted to uppercase.
#
# In case the length of the comment exceeds the length of the dashed line,
# the comment line is allowed to stick out to the right.
#
# If the length of the comment is less than the dashed line, it is centered
# relative to the # length of the dashed line.

# If the length of the comment and this line do not divide, an extra space is
# added to the left of the comment.
#
# For example, if the comment is 55 long, there are 5 spaces to be distributed.
# There will be 3 spaces, followed by the comment.
#------------------------------------------------------------------------------
sub print_table_user_settings
{
  my $subr_name = get_my_name ();

  my ($mode, $comment) = @_;

  my $data_type;
  my $debug_size_value = $g_user_settings{"debug"}{"current_value"};
  my $db_size;
  my $defined;
  my $keyword;
  my $leftover;
  my $padding;
  my $user_option;
  my $value;

  my $HEADER_LIMIT = 79;
  my $header = sprintf ("%-20s   %-22s   %8s   %s",
                        "keyword", "option", "user set", "internal value");

#------------------------------------------------------------------------------
# Generate the dashed line
#------------------------------------------------------------------------------
  my $dashes = "-";
  for (2 .. $HEADER_LIMIT)
    {
      $dashes .= "-";
    }

#------------------------------------------------------------------------------
# Determine the padding needed to the left of the comment.
#------------------------------------------------------------------------------
  my $length_comment = length ($comment);

  $leftover = $length_comment%2;

  if ($length_comment <= ($HEADER_LIMIT-2))
    {
      $padding = ($HEADER_LIMIT - $length_comment + $leftover)/2;
    }
  else
    {
      $padding = 0;
    }
 
#------------------------------------------------------------------------------
# Generate the first blank part of the line.
#------------------------------------------------------------------------------
  my $blank_line = "";
  for (1 .. $padding)
    {
      $blank_line .= " ";
    }

#------------------------------------------------------------------------------
# Add the comment line with the first letter in uppercase.
#------------------------------------------------------------------------------
  my $final_comment = $blank_line.ucfirst ($comment);

  gp_message ($mode, $subr_name, $dashes);
  gp_message ($mode, $subr_name, $final_comment);
  gp_message ($mode, $subr_name, $dashes);
  gp_message ($mode, $subr_name, $header);
  gp_message ($mode, $subr_name, $dashes);

#------------------------------------------------------------------------------
# Print a line for each option. The list is sorted alphabetically.
#------------------------------------------------------------------------------
  for my $key  (sort keys %g_user_settings)
    {
      $keyword     = $key;
      $user_option = $g_user_settings{$key}{"option"};
      $defined     = ($g_user_settings{$key}{"defined"} ? "set" : "not set");
      $data_type   = $g_user_settings{$key}{"data_type"};

      if (defined ($g_user_settings{$key}{"current_value"}))
        {
          $value = $g_user_settings{$key}{"current_value"};
          if ($data_type eq "boolean")
            {
              $value = $value ? "on" : "off";
            }
#------------------------------------------------------------------------------
# In case of the debug option, we add the "(size)" string to remind the user
# that this is the size.
#------------------------------------------------------------------------------
          if ($key eq "debug")
            {
              $db_size = ($debug_size_value eq "on") ? "s" : $debug_size_value;
              $value = $db_size . " (size)";
            }
        }
      else
        {
          $value = "undefined";
        }

      my $print_line = sprintf ("%-20s   %-22s   %8s   %s",
                                $keyword, $user_option, $defined, $value);

      gp_message ($mode, $subr_name, $print_line);
    }
} #-- End of subroutine print_table_user_settings

#------------------------------------------------------------------------------
# Dump the contents of nested hash "g_user_settings".  Some simple formatting
# is applied to make it easier to distinguish the various values.
#------------------------------------------------------------------------------
sub print_user_settings
{
  my $subr_name = get_my_name ();

  my ($mode, $comment) = @_;

  my $keyword_value_pair;

  gp_message ($mode, $subr_name, $comment);

  for my $key (keys %g_user_settings)
    {
      my $print_line = sprintf ("%-20s =>", $key);
      for my $fields (sort keys %{ $g_user_settings{$key} })
        {
          if (defined ($g_user_settings{$key}{$fields}))
            {
              $keyword_value_pair = $fields." = ".$g_user_settings{$key}{$fields};
            }
          else
            {
              $keyword_value_pair = $fields." = ". "undefined";
            }
           $print_line = join ("  ", $print_line, $keyword_value_pair);
        }
        gp_message ($mode, $subr_name, $print_line);
    }
} #-- End of subroutine print_user_settings

#------------------------------------------------------------------------------
# Print the version number and license information.
#------------------------------------------------------------------------------
sub print_version_info
{
  print "$version_info\n";
  print "Copyright (C) 2025 Free Software Foundation, Inc.\n";
  print "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.\n";
  print "This is free software: you are free to change and redistribute it.\n";
  print "There is NO WARRANTY, to the extent permitted by law.\n";

  return (0);

} #-- End of subroutine print_version_info

#------------------------------------------------------------------------------
# Dump all the warning messages in the buffer.
#------------------------------------------------------------------------------
sub print_warnings_buffer
{
  my $subr_name = get_my_name ();

  my $ignore_value;
  my $msg;

  if (not $g_options_printed)
#------------------------------------------------------------------------------
# Only if the options have not yet been printed, print them.
#------------------------------------------------------------------------------
    {
      $g_options_printed = $TRUE;
      $ignore_value = print_command_line_options (\$g_warn_keyword);
    }

  for my $i (keys @g_warning_msgs)
    {
      $msg = $g_warning_msgs[$i];
      if ($msg =~ /^$g_html_new_line/)
        {
          $msg =~ s/$g_html_new_line//;
          printf ("%-9s\n", $g_warn_keyword);
        }
      printf ("%-9s %s\n", $g_warn_keyword, ucfirst ($msg));
    }

  return (0);

} #-- End of subroutine print_warnings_buffer

#------------------------------------------------------------------------------
# Process the call tree input data and generate HTML output.
#------------------------------------------------------------------------------
sub process_calltree
{
  my $subr_name = get_my_name ();

  my ($function_info_ref, $function_address_info_ref, $addressobjtextm_ref,
       $input_string) = @_;

  my @function_info         = @{ $function_info_ref };
  my %function_address_info = %{ $function_address_info_ref };
  my %addressobjtextm       = %{ $addressobjtextm_ref };

  my $outputdir = append_forward_slash ($input_string);

  my @call_tree_data = ();

  my $LANG              = $g_locale_settings{"LANG"};
  my $decimal_separator = $g_locale_settings{"decimal_separator"};

  my $infile  = $outputdir . "calltree";
  my $outfile = $outputdir . "calltree.html";

  open (CALL_TREE_IN, "<", $infile)
    or die ("Not able to open calltree file $infile for reading - '$!'");
  gp_message ("debug", $subr_name, "opened file $infile for reading");

  open (CALL_TREE_OUT, ">", $outfile)
    or die ("Not able to open $outfile for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile for writing");

  gp_message ("debug", $subr_name, "building calltree file $outfile");

#------------------------------------------------------------------------------
# The directory name is potentially used below, but since it is a constant,
# we get it here and only once.
#------------------------------------------------------------------------------
#  my ($ignore_file_name, $directory_name, $ignore_suffix) = fileparse ($infile,"");
#  gp_message ("debug", $subr_name, "directory_name = $directory_name");

#------------------------------------------------------------------------------
# Generate some of the structures used in the HTML output.
#------------------------------------------------------------------------------
  my $file_title      = "Call Tree overview";
  my $html_header     = ${ create_html_header (\$file_title) };
  my $html_home_right = ${ generate_home_link ("right") };

  my $page_title    = "Call Tree View";
  my $size_text     = "h2";
  my $position_text = "center";
  my $html_title_header = ${ generate_a_header (
                            \$page_title,
                            \$size_text,
                            \$position_text) };

#------------------------------------------------------------------------------
# Get the acknowledgement, return to main link, and final html statements.
#------------------------------------------------------------------------------
  my $html_home_left       = ${ generate_home_link ("left") };
  my $html_acknowledgement = ${ create_html_credits () };
  my $html_end             = ${ terminate_html_document () };

#------------------------------------------------------------------------------
# Read all of the file into array with the name call_tree_data.
#------------------------------------------------------------------------------
  chomp (@call_tree_data = <CALL_TREE_IN>);
  close (CALL_TREE_IN);

#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Process the data here and generate the HTML lines.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Print the top part of the HTML file.
#------------------------------------------------------------------------------
  print CALL_TREE_OUT $html_header;
  print CALL_TREE_OUT $html_home_right;
  print CALL_TREE_OUT $html_title_header;

#------------------------------------------------------------------------------
# Print the generated HTML structures here.
#------------------------------------------------------------------------------
##  print CALL_TREE_OUT "$_" for @whatever;
##  print CALL_TREE_OUT "<pre>\n";
##  print CALL_TREE_OUT "$_\n" for @whatever2;
##  print CALL_TREE_OUT "</pre>\n";

#------------------------------------------------------------------------------
# Print the last part of the HTML file.
#------------------------------------------------------------------------------
  print CALL_TREE_OUT $html_home_left;
  print CALL_TREE_OUT "<br>\n";
  print CALL_TREE_OUT $html_acknowledgement;
  print CALL_TREE_OUT $html_end;

  close (CALL_TREE_OUT);

  return (0);

} #-- End of subroutine process_calltree

#------------------------------------------------------------------------------
# Process the generated experiment info file(s).
#------------------------------------------------------------------------------
sub process_experiment_info
{
  my $subr_name = get_my_name ();

  my ($experiment_data_ref) = @_;

  my @exp_info;
  my @experiment_data = @{ $experiment_data_ref };

  my $exp_id;
  my $exp_name;
  my $exp_data_file;
  my $input_line;
  my $target_cmd;
  my $hostname ;
  my $OS;
  my $page_size;
  my $architecture;
  my $start_date;
  my $end_experiment;
  my $data_collection_duration;
  my $total_thread_time;
  my $user_cpu_time;
  my $user_cpu_percentage;
  my $system_cpu_time;
  my $system_cpu_percentage;
  my $sleep_time;
  my $sleep_percentage;

#------------------------------------------------------------------------------
# Define the regular expressions used to capture the info.
#------------------------------------------------------------------------------
# Target command (64-bit): './../bindir/mxv-pthreads.exe -m 3000 -n 2000 -t 2'

  my $target_cmd_regex = '\s*Target command\s+(\(.+\)):\s+\'(.+)\'';

# Host `ruudvan-vm-haswell-2-20210609', OS `Linux 5.4.17-2102.202.5.el8uek.x86_64', page size 4096, architecture `x86_64'

  my $host_system_regex = '\s*Host\s+\`(.+)\',\s+OS\s+\`(.+)\',\s+page size\s+(\d+),\s+architecture\s+\`(.+)\'';

# Experiment started Mon Aug 30 13:03:20 2021

  my $start_date_regex = '\s*Experiment started\s+(.+)';

# Experiment Ended: 1.812441219

  my $end_experiment_regex = '\s*Experiment Ended:\s+(.+)';

# Data Collection Duration: 1.812441219

  my $data_collection_duration_regex = '\s*Data Collection Duration:\s+(.+)';

#                           Total Thread Time (sec.): 1.812

  my $total_thread_time_regex = '\s*Total Thread Time (sec.):\s+(.+)';

#                                          User CPU: 1.685 ( 95.0%)

  my $user_cpu_regex = '\s*User CPU:\s+(.+)\s+\(\s*(.+)\)';

#                                        System CPU: 0.088 (  5.0%)

  my $system_cpu_regex = '\s*System CPU:\s+(.+)\s+\(\s*(.+)\)';

#                                             Sleep: 0.    (  0. %)

  my $sleep_regex = '\s*Sleep:\s+(.+)\s+\(\s*(.+)\)';

#------------------------------------------------------------------------------
# Scan the experiment data and select the info of interest.
#------------------------------------------------------------------------------
  for my $i (sort keys @experiment_data)
    {
      $exp_id        = $experiment_data[$i]{"exp_id"};
      $exp_name      = $experiment_data[$i]{"exp_name_full"};
      $exp_data_file = $experiment_data[$i]{"exp_data_file"};

      my $msg = "exp_id = $exp_id name = $exp_name file = $exp_data_file";
      gp_message ("debug", $subr_name, $msg);

      open (EXPERIMENT_INFO, "<", $exp_data_file)
        or die ("$subr_name - unable to open file $exp_data_file for reading '$!'");
      gp_message ("debug", $subr_name, "opened file $exp_data_file for reading");

      chomp (@exp_info = <EXPERIMENT_INFO>);

#------------------------------------------------------------------------------
# Process the info for the current experiment.
#------------------------------------------------------------------------------
      for my $line (0 .. $#exp_info)
        {
          $input_line = $exp_info[$line];

          my $msg = "exp_id = $exp_id: input_line = $input_line";
          gp_message ("debugM", $subr_name, $msg);

          if ($input_line =~ /$target_cmd_regex/)
            {
              $target_cmd = $2;
              gp_message ("debugM", $subr_name, "$exp_id => $target_cmd");
              $experiment_data[$i]{"target_cmd"} = $target_cmd;
            }
          elsif ($input_line =~ /$host_system_regex/)
            {
              $hostname  = $1;
              $OS        = $2;
              $page_size = $3;
              $architecture = $4;
              gp_message ("debugM", $subr_name, "$exp_id => $hostname $OS $page_size $architecture");
              $experiment_data[$i]{"hostname"} = $hostname;
              $experiment_data[$i]{"OS"} = $OS;
              $experiment_data[$i]{"page_size"} = $page_size;
              $experiment_data[$i]{"architecture"} = $architecture;
            }
          elsif ($input_line =~ /$start_date_regex/)
            {
              $start_date = $1;
              gp_message ("debugM", $subr_name, "$exp_id => $start_date");
              $experiment_data[$i]{"start_date"} = $start_date;
            }
          elsif ($input_line =~ /$end_experiment_regex/)
            {
              $end_experiment = $1;
              gp_message ("debugM", $subr_name, "$exp_id => $end_experiment");
              $experiment_data[$i]{"end_experiment"} = $end_experiment;
            }
          elsif ($input_line =~ /$data_collection_duration_regex/)
            {
              $data_collection_duration = $1;
              gp_message ("debugM", $subr_name, "$exp_id => $data_collection_duration");
              $experiment_data[$i]{"data_collection_duration"} = $data_collection_duration;
            }
#------------------------------------------------------------------------------
#                                       Start Label: Total
#                                          End Label: Total
#                                  Start Time (sec.): 0.000
#                                    End Time (sec.): 1.812
#                                    Duration (sec.): 1.812
#                           Total Thread Time (sec.): 1.812
#                          Average number of Threads: 1.000
#
#                               Process Times (sec.):
#                                           User CPU: 1.666 ( 91.9%)
#                                         System CPU: 0.090 (  5.0%)
#                                           Trap CPU: 0.    (  0. %)
#                                          User Lock: 0.    (  0. %)
#                                    Data Page Fault: 0.    (  0. %)
#                                    Text Page Fault: 0.    (  0. %)
#                                  Kernel Page Fault: 0.    (  0. %)
#                                            Stopped: 0.    (  0. %)
#                                           Wait CPU: 0.    (  0. %)
#                                              Sleep: 0.056 (  3.1%)
#------------------------------------------------------------------------------
          elsif ($input_line =~ /$total_thread_time_regex/)
            {
              $total_thread_time = $1;
              gp_message ("debugM", $subr_name, "$exp_id => $total_thread_time");
              $experiment_data[$i]{"total_thread_time"} = $total_thread_time;
            }
          elsif ($input_line =~ /$user_cpu_regex/)
            {
              $user_cpu_time       = $1;
              $user_cpu_percentage = $2;
              gp_message ("debugM", $subr_name, "$exp_id => $user_cpu_time $user_cpu_percentage");
              $experiment_data[$i]{"user_cpu_time"} = $user_cpu_time . "&nbsp; (" . $user_cpu_percentage . ")";
              $experiment_data[$i]{"user_cpu_percentage"} = $user_cpu_percentage;
            }
          elsif ($input_line =~ /$system_cpu_regex/)
            {
              $system_cpu_time       = $1;
              $system_cpu_percentage = $2;
              gp_message ("debugM", $subr_name, "$exp_id => $system_cpu_time $system_cpu_percentage");
              $experiment_data[$i]{"system_cpu_time"} = $system_cpu_time . "&nbsp; (" . $system_cpu_percentage . ")";
              $experiment_data[$i]{"system_cpu_percentage"} = $system_cpu_percentage;
            }
          elsif ($input_line =~ /$sleep_regex/)
            {
              $sleep_time       = $1;
              $sleep_percentage = $2;
              $experiment_data[$i]{"sleep_time"} = $sleep_time . "&nbsp; (" . $sleep_percentage . ")";
              $experiment_data[$i]{"sleep_percentage"} = $sleep_percentage;

              my $msg = "exp_id = $exp_id => sleep_time = $sleep_time " .
                        "sleep_percentage = $sleep_percentage";
              gp_message ("debugM", $subr_name, $msg);
            }
        }
    }

  for my $keys (0 .. $#experiment_data)
    {
      for my $fields (sort keys %{ $experiment_data[$keys] })
        {
          my $msg = "experiment_data[$keys]{$fields} = " .
             $experiment_data[$keys]{$fields};
          gp_message ("debugM", $subr_name, $msg);
        }
    }

  return (\@experiment_data);

} #-- End of subroutine process_experiment_info

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub process_function_files
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref, $executable_name, $time_percentage_multiplier,
      $summary_metrics, $process_all_functions, $elf_loadobjects_found,
      $outputdir, $sort_fields_ref, $function_info_ref,
      $function_address_and_index_ref, $LINUX_vDSO_ref,
      $metric_description_ref, $elf_arch, $base_va_executable,
      $ARCHIVES_MAP_NAME, $ARCHIVES_MAP_VADDR, $elf_rats_ref) = @_;

  my $old_fsummary;
  my $total_attributed_time;
  my $current_attributed_time;
  my $value;

  my @exp_dir_list               = @{ $exp_dir_list_ref };
  my @function_info              = @{ $function_info_ref };
  my %function_address_and_index = %{ $function_address_and_index_ref };
  my @sort_fields                = @{ $sort_fields_ref };
  my %metric_description         = %{ $metric_description_ref };
  my %elf_rats                   = %{ $elf_rats_ref };

#------------------------------------------------------------------------------
# The regex section.
#
# TBD: Remove the part regarding clones. Legacy.
#------------------------------------------------------------------------------
  my $find_clone_regex    = '^(.*)(\s+--\s+cloned\s+version\s+\[)([^]]+)(\])';
  my $remove_number_regex = '^\d+:';
  my $replace_quote_regex = '"/\"';

  my %addressobj_index = ();
  my %function_address_info = ();
  my $function_address_info_ref;

  $outputdir = append_forward_slash ($outputdir);

  my %functions_per_metric_indexes = ();
  my $functions_per_metric_indexes_ref;

  my %functions_per_metric_first_index = ();
  my $functions_per_metric_first_index_ref;

  my %routine_list = ();
  my %handled_routines = ();

#------------------------------------------------------------------------------
# TBD: Name cleanup needed.
#------------------------------------------------------------------------------
  my $msg;

  my $number_of_metrics;
  my $expr_name;
  my $routine;
  my $tmp;
  my $loadobj;
  my $PCA;
  my $address_field;
  my $limit_txt;
  my $n_metrics_text;
  my $disfile;
  my $srcfile;
  my $RIN;
  my $gp_listings_cmd;
  my $gp_display_text_cmd;
  my $ignore_value;

  my $result_file   = $outputdir . "gp-listings.out";
  my $gp_error_file = $outputdir . "gp-listings.err";

  my $convert_to_dot    = $g_locale_settings{"convert_to_dot"};
  my $decimal_separator = $g_locale_settings{"decimal_separator"};
  my $length_of_string  = length ($outputdir);

  $expr_name = join (" ", @exp_dir_list);

  gp_message ("debug", $subr_name, "expr_name = $expr_name");

#------------------------------------------------------------------------------
# Loop over the files in $outputdir.
#------------------------------------------------------------------------------
  while (glob ($outputdir.'*.sort.func-PC'))
    {
      my $metric;
      my $infile;
      my $ignore_value;
      my $suffix_not_used;

      $infile = $_;

      ($metric, $ignore_value, $suffix_not_used) = fileparse ($infile, ".sort.func-PC");

      gp_message ("debugXL", $subr_name, "suffix_not_used = $suffix_not_used");
      gp_message ("debugXL", $subr_name, "func-PC->$infile<- metric->$metric<-");

   # Function_info creates the functions files from the PC ones
   # as well as culling PC and metric information

      ($function_address_info_ref,
       $functions_per_metric_first_index_ref,
       $functions_per_metric_indexes_ref) = function_info (
                                              $outputdir,
                                              $infile,
                                              $metric,
                                              $LINUX_vDSO_ref);

      @{$function_address_info{$metric}}            = @{$function_address_info_ref};
      %{$functions_per_metric_indexes{$metric}}     = %{$functions_per_metric_indexes_ref};
      %{$functions_per_metric_first_index{$metric}} = %{$functions_per_metric_first_index_ref};

      $ignore_value = print_metric_function_array ($metric,
                                                   "function_address_info",
                                                   \@{$function_address_info{$metric}});
      $ignore_value = print_metric_function_hash ("hash_hash",  $metric,
                                                  "functions_per_metric_first_index",
                                                  \%{$functions_per_metric_first_index{$metric}});
      $ignore_value = print_metric_function_hash ("hash_array", $metric,
                                                  "functions_per_metric_indexes",
                                                  \%{$functions_per_metric_indexes{$metric}});
    }

#------------------------------------------------------------------------------
# Get header info for use in post processing er_html output
#------------------------------------------------------------------------------
  gp_message ("debugXL", $subr_name, "get_hdr_info section");

  get_hdr_info ($outputdir, $outputdir."functions.sort.func");

  for my $field (@sort_fields)
    {
      get_hdr_info ($outputdir, $outputdir."$field.sort.func");
    }

#------------------------------------------------------------------------------
# Caller-callee
#------------------------------------------------------------------------------
  get_hdr_info ($outputdir, $outputdir."calls.sort.func");

#------------------------------------------------------------------------------
# Calltree
#------------------------------------------------------------------------------
  if ($g_user_settings{"calltree"}{"current_value"} eq "on")
    {
      get_hdr_info ($outputdir, $outputdir."calltree.sort.func");
    }

  gp_message ("debug", $subr_name, "process functions");

  my $scriptfile     = $outputdir.'gp-script';
  my $script_metrics = "$summary_metrics";
  my $func_limit     = $g_user_settings{"func_limit"}{"current_value"};

  open (SCRIPT, ">", $scriptfile)
    or die ("Unable to create script file $scriptfile - '$!'");
  gp_message ("debug", $subr_name, "opened script file $scriptfile for writing");

  print SCRIPT "# limit $func_limit\n";
  print SCRIPT "limit $func_limit\n";
  print SCRIPT "# thread_select all\n";
  print SCRIPT "thread_select all\n";
  print SCRIPT "# metrics $script_metrics\n";
  print SCRIPT "metrics $script_metrics\n";

  for my $metric (@sort_fields)
    {
      gp_message ("debug", $subr_name, "handling $metric->$metric_description{$metric}");

      $total_attributed_time   = 0;
      $current_attributed_time = 0;

      $value = $function_address_info{$metric}[0]{"metric_value"}; # <Total>
      if ($convert_to_dot)
        {
          $value =~ s/$decimal_separator/\./;
        }
      $total_attributed_time = $value;

#------------------------------------------------------------------------------
# start at 1 - skipping <Total>
#------------------------------------------------------------------------------
      for my $INDEX (1 .. $#{$function_address_info{$metric}})
        {
#------------------------------------------------------------------------------
#Looking to handle at least 99% of the time - or what the user asked for
#------------------------------------------------------------------------------
          $value   = $function_address_info{$metric}[$INDEX]{"metric_value"};
          $routine = $function_address_info{$metric}[$INDEX]{"routine"};

          gp_message ("debugXL", $subr_name, " total $total_attributed_time current $current_attributed_time");
          gp_message ("debugXL", $subr_name, "  (found routine $routine : value $value)");

          if ($convert_to_dot)
            {
              $value =~ s/$decimal_separator/\./;
            }

          if ( ($value > $total_attributed_time*(1-$time_percentage_multiplier)) or
               ( ($total_attributed_time == 0) and ($value>0) ) or
               $process_all_functions)
            {
              $PCA = $function_address_info{$metric}[$INDEX]{"PC Address"};

              if (not exists ($functions_per_metric_first_index{$metric}{$routine}{$PCA}))
                {
                  gp_message ("debugXL", $subr_name, "not exists: functions_per_metric_first_index{$metric}{$routine}{$PCA}");
                }
              if (not exists ($function_address_and_index{$routine}{$PCA}))
                {
                  gp_message ("debugXL", $subr_name, "not exists: function_address_and_index{$routine}{$PCA}");
                }

              if (exists ($functions_per_metric_first_index{$metric}{$routine}{$PCA}) and
                  exists ($function_address_and_index{$routine}{$PCA}))
                {
#------------------------------------------------------------------------------
# handled_routines now contains $RI from "first_metric" (?)
#------------------------------------------------------------------------------
                  $handled_routines{$function_address_and_index{$routine}{$PCA}} = 1;
                  my $description = ${ retrieve_metric_description (\$metric, \%metric_description) };
                  if ($metric_description{$metric} =~ /Exclusive Total CPU Time/)
                    {
                      $routine_list{$routine} = 1
                    }

                  gp_message ("debugXL", $subr_name, " $routine is candidate");
                }
              else
                {
                  die ("internal error for metric $metric and routine $routine");
                }

              $current_attributed_time += $value;
            }
        }
    }
#------------------------------------------------------------------------------
# Sort numerically in ascending order.
#------------------------------------------------------------------------------
  for my $routine_index (sort {$a <=> $b} keys %handled_routines)
    {
      $routine = $function_info[$routine_index]{"routine"};
      gp_message ("debugXL", $subr_name, "routine_index = $routine_index routine = $routine");
      next unless $routine_list{$routine};

# not used      $source = $function_info[$routine_index]{"Source File"};

      $function_info[$routine_index]{"srcline"} = "";
      $address_field = $function_info[$routine_index]{"addressobjtext"};

#------------------------------------------------------------------------------
# Strip the internal number from the address field.
#------------------------------------------------------------------------------
      $msg = "address_field before regex = " . $address_field;
      gp_message ("debugXL", $subr_name, $msg);
      $address_field =~ s/$remove_number_regex//;
      $msg = "address_field after  regex = " . $address_field;
      gp_message ("debugXL", $subr_name, $msg);

##      $disfile = "file\.$routine_index\.dis";
      $disfile = "file." . $routine_index . "." . $g_html_base_file_name{"disassembly"};
      $srcfile = "";
      $srcfile = "file\.$routine_index\.src.txt";

#------------------------------------------------------------------------------
# If the file is unknown, we can disassemble anyway and add disassembly
# to the script.
#------------------------------------------------------------------------------
      print SCRIPT "# outfile $outputdir"."$disfile\n";
      print SCRIPT "outfile $outputdir"."$disfile\n";
#------------------------------------------------------------------------------
# TBD: Legacy. Not sure why this is needed, but it won't harm things. I hope.
#------------------------------------------------------------------------------
      $tmp = $routine;
      $tmp =~ s/$replace_quote_regex//g;
      print SCRIPT "# disasm \"$tmp\" $address_field\n";
#------------------------------------------------------------------------------
## TBD: adding the address is not supported.  Need to find a way to figure
## out the ID of the function.
##      print SCRIPT "disasm \"$tmp\" $address_field\n";
##      print SCRIPT "source \"$tmp\" $address_field\n";
#------------------------------------------------------------------------------
      print SCRIPT "disasm \"$tmp\"\n";
      if ($srcfile=~/file/)
        {
          print SCRIPT "# outfile $outputdir"."$srcfile\n";
          print SCRIPT "outfile $outputdir"."$srcfile\n";
          print SCRIPT "# source \"$tmp\" $address_field\n";
          print SCRIPT "source \"$tmp\"\n";
        }

      if ($routine =~ /$find_clone_regex/)
        {
          my ($clone_routine) = $1.$2.$3.$4;
          my ($clone) = $3;
        }
     }
  close SCRIPT;

#------------------------------------------------------------------------------
# Remember the number of handled routines depends on the limit setting passed
# to er_print together with the sorting order on the metrics, which usually results
# in different routines at the top. Thus $RIN below can be greater than the limit.
#------------------------------------------------------------------------------

  $RIN = scalar (keys %handled_routines);

  if (!$func_limit)
    {
      $limit_txt = "unlimited";
    }
  else
    {
      $limit_txt = $func_limit - 1;
  }

  $number_of_metrics = scalar (@sort_fields);

  $n_metrics_text = ($number_of_metrics == 1) ? "metric" : "metrics";

  gp_message ("debugXL", $subr_name, "built function list with $RIN functions");
  gp_message ("debugXL", $subr_name, "$number_of_metrics $n_metrics_text and a function limit of $limit_txt");

# add ELF program header offset

  for my $routine_index (sort {$a <=> $b} keys %handled_routines)
    {
      $routine = $function_info[$routine_index]{"routine"};
      $loadobj = $function_info[$routine_index]{"Load Object"};

      gp_message ("debugXL", $subr_name, "routine = $routine loadobj = $loadobj elf_arch = $elf_arch");

      if ($loadobj ne '')
        {
    # <Truncated-stack> is associated with <Total>. Its load object is <Total>
          if ($loadobj eq "<Total>")
            {
              next;
            }
    # Have seen a routine called <Unknown>. Its load object is <Unknown>
          if ($loadobj eq "<Unknown>")
            {
              next;
            }
###############################################################################
## RUUD: The new approach gives a different result. Investigate this.
#
# Turns out the new code improves the result.  The addresses are now correct
# and as a result, more ftag's are created later on.
###############################################################################
          gp_message ("debugXL", $subr_name, "before function_info[$routine_index]{addressobj} = $function_info[$routine_index]{'addressobj'}");

          $function_info[$routine_index]{"addressobj"} += bigint::hex (
                                                determine_base_va_address (
                                                  $executable_name,
                                                  $base_va_executable,
                                                  $loadobj,
                                                  $routine));
          $addressobj_index{$function_info[$routine_index]{"addressobj"}} = $routine_index;

          gp_message ("debugXL", $subr_name, "after  function_info[$routine_index]{addressobj} = $function_info[$routine_index]{'addressobj'}");
          gp_message ("debugXL", $subr_name, "after  addressobj_index{function_info[$routine_index]{addressobj}} = $addressobj_index{$function_info[$routine_index]{'addressobj'}}");
        }
    }

#------------------------------------------------------------------------------
# Get the disassembly and source code output.
#------------------------------------------------------------------------------
  $gp_listings_cmd = "$GP_DISPLAY_TEXT -limit $func_limit -viewmode machine " .
                     "-compare off -script $scriptfile $expr_name";

  $gp_display_text_cmd = "$gp_listings_cmd 1> $result_file 2>> $gp_error_file";

  gp_message ("debugXL", $subr_name,"gp_display_text_cmd = $gp_display_text_cmd");

  gp_message ("debug", $subr_name, "calling $GP_DISPLAY_TEXT to produce disassembly and source code output");

  my ($error_code, $cmd_output) = execute_system_cmd ($gp_display_text_cmd);

  if ($error_code != 0)
    {
      $ignore_value = msg_display_text_failure ($gp_display_text_cmd,
                                                $error_code,
                                                $gp_error_file);
      gp_message ("abort", $subr_name, "execution terminated");
    }

  return (\@function_info, \%function_address_info, \%addressobj_index);

} #-- End of subroutine process_function_files

#------------------------------------------------------------------------------
# Process the information found in the function overview file passed in.
#
# Example input:
#
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Functions sorted by metric: Exclusive Total CPU Time
#
# PC Addr.       Name              Excl.     Excl. CPU  Excl.         Excl.         Excl.   Excl.
#                                  Total     Cycles     Instructions  Last-Level    IPC     CPI
#                                  CPU sec.   sec.      Executed      Cache Misses
# 1:0x00000000   <Total>           3.713     4.256      15396819712   27727992       1.577  0.634
# 2:0x000021ae   mxv_core          3.532     4.116      14500538992   27527781       1.536  0.651
# 2:0x00001f7b   init_data         0.070     0.084         64020034     200211       0.333  3.000
#------------------------------------------------------------------------------
sub process_function_overview
{
  my $subr_name = get_my_name ();

  my ($metric_ref, $exp_type_ref, $summary_metrics_ref, $number_of_metrics_ref,
      $function_info_ref, $function_view_structure_ref, $overview_file_ref) = @_;

  my $metric                  = ${ $metric_ref };
  my $exp_type                = ${ $exp_type_ref };
  my $summary_metrics         = ${ $summary_metrics_ref };
  my $number_of_metrics       = ${ $number_of_metrics_ref };
  my @function_info           = @{ $function_info_ref };
  my %function_view_structure = %{ $function_view_structure_ref };
  my $overview_file           = ${ $overview_file_ref };

  my $all_metrics;
  my $decimal_separator = $g_locale_settings{"decimal_separator"};
  my $length_of_block;
  my $elements_in_name;
  my $full_hex_address;
  my $header_line;
  my $hex_address;
  my $html_line;
  my $input_line;
  my $marker;
  my $name_regex;
  my $no_of_fields;
  my $metrics_length;
  my $missing_digits;
  my $msg;
  my $remaining_part_header;
  my $routine;
  my $routine_length;
  my $scan_header        = $FALSE;
  my $scan_function_data = $FALSE;
  my $string_length;
  my $total_header_lines;

  my @address_field           = ();
  my @fields                  = ();
  my @function_data           = ();
  my @function_names          = ();
  my @function_view_array     = ();
  my @function_view_modified  = ();
  my @header_lines            = ();
  my @metrics_part            = ();
  my @metric_values           = ();

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $header_name_regex     = '(.*\.)(\s+)(Name)\s+(.*)';
  my $total_marker_regex    = '\s*(\d+:0x[a-fA-F0-9]+)\s+(<Total>)\s+(.*)';
  my $empty_line_regex      = '^\s*$';
  my $catch_all_regex       = '\s*(.*)';
  my $get_hex_address_regex = '(\d+):0x(\S+)';
  my $get_addr_offset_regex = '^@\d+:';
  my $zero_dot_at_end_regex = '[\w0-9' . $decimal_separator . ']*(0' . $decimal_separator . '$)';
  my $backward_slash_regex  = '\/';

  $msg = "enter subroutine " . $subr_name;
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
  if (is_file_empty ($overview_file))
    {
      gp_message ("assertion", $subr_name, "file $overview_file is empty");
    }

  open (FUNC_OVERVIEW, "<", $overview_file)
    or die ("$subr_name - unable to open file $overview_file for reading '$!'");
  gp_message ("debug", $subr_name, "opened file $overview_file for reading");

  gp_message ("debug", $subr_name, "processing file for exp_type = $exp_type");

  gp_message ("debugM", $subr_name, "header_name_regex  = $header_name_regex");
  gp_message ("debugM", $subr_name, "total_marker_regex = $total_marker_regex");
  gp_message ("debugM", $subr_name, "empty_line_regex   = $empty_line_regex");
  gp_message ("debugM", $subr_name, "catch_all_regex    = $catch_all_regex");
  gp_message ("debugM", $subr_name, "get_hex_address_regex = $get_hex_address_regex");
  gp_message ("debugM", $subr_name, "get_addr_offset_regex = $get_addr_offset_regex");
  gp_message ("debugM", $subr_name, "zero_dot_at_end_regex = $zero_dot_at_end_regex");
  gp_message ("debugM", $subr_name, "backward_slash_regex  = $backward_slash_regex");

#------------------------------------------------------------------------------
# Read the input file into memory.
#------------------------------------------------------------------------------
  chomp (@function_data = <FUNC_OVERVIEW>);
  gp_message ("debug", $subr_name, "read all of file $overview_file into memory");

#------------------------------------------------------------------------------
# Remove a legacy redundant string, if any.
#------------------------------------------------------------------------------
  @function_data = @{ remove_redundant_string (\@function_data)};

#------------------------------------------------------------------------------
# Parse the function view info and store the data.
#------------------------------------------------------------------------------
  my $max_header_length  = 0;
  my $max_metrics_length = 0;

#------------------------------------------------------------------------------
# Loop over all the lines.  Extract the header, metric values, function names,
# and the addresses.
#
# This is also where the maximum lengths for the header and metric lines are
# computed.  This is used to get the correct alignment in the HTML output.
#------------------------------------------------------------------------------
  for (my $line = 0; $line <= $#function_data; $line++)
    {
      $input_line = $function_data[$line];
##      $input_line =~ s/ --  no functions found//;

      gp_message ("debugXL", $subr_name, "input_line = $input_line");

#------------------------------------------------------------------------------
# The table header is assumed to start at the line that has "Name" in it.
# The header ends when we see the function name "<Total>".
#------------------------------------------------------------------------------
      if ($input_line =~ /$header_name_regex/)
        {
          $scan_header = $TRUE;
        }
      elsif ($input_line =~ /$total_marker_regex/)
        {
          $scan_header        = $FALSE;
          $scan_function_data = $TRUE;
        }

      if ($scan_header)
        {
#------------------------------------------------------------------------------
# This group is only defined for the first line of the header and $4 contains
# the remaining part of the line after "Name", without the leading spaces.
#------------------------------------------------------------------------------
          if (defined ($4))
            {
              $remaining_part_header = $4;
              $msg =  "remaining_part_header = $remaining_part_header";
              gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Determine the maximum length of the header.  This needs to be done before
# the HTML controls are added.
#------------------------------------------------------------------------------
              my $header_length = length ($remaining_part_header);
              $max_header_length = max ($max_header_length, $header_length);

#------------------------------------------------------------------------------
# TBD Should change this and not yet include html in header_lines
#------------------------------------------------------------------------------
              $html_line = "<b>" . $remaining_part_header . "</b>";

              push (@header_lines, $html_line);

              gp_message ("debugXL", $subr_name, "max_header_length = $max_header_length");
              gp_message ("debugXL", $subr_name, "html_line = $html_line");
            }
#------------------------------------------------------------------------------
# Captures the subsequent header lines.  Assume they exist.
#------------------------------------------------------------------------------
          elsif ($input_line =~ /$catch_all_regex/)
            {
              $header_line = $1;
              gp_message ("debugXL", $subr_name, "header_line = $header_line");

              my $header_length = length ($header_line);
              $max_header_length = max ($max_header_length, $header_length);

#------------------------------------------------------------------------------
# TBD Should change this and not yet include html in header_lines
#------------------------------------------------------------------------------
              $html_line = "<b>" . $header_line . "</b>";

              push (@header_lines, $html_line);

              gp_message ("debugXL", $subr_name, "max_header_length = $max_header_length");
              gp_message ("debugXL", $subr_name, "html_line = $html_line");
            }
        }
#------------------------------------------------------------------------------
# This is a line with function data.
#------------------------------------------------------------------------------
      if ($scan_function_data and (not ($input_line =~ /$empty_line_regex/)))
        {
          $msg = "detected a line with function data";
          gp_message ("debugXL", $subr_name, $msg);

          my ($hex_address_ref, $marker_ref, $reduced_line_ref, 
              $list_with_metrics_ref) =
                                       split_function_data_line (\$input_line);

          $full_hex_address  = ${ $hex_address_ref };
          $marker            = ${ $marker_ref };
          $routine           = ${ $reduced_line_ref };
          $all_metrics       = ${ $list_with_metrics_ref };

          $msg = "RESULT full_hex_address = " . $full_hex_address;
          $msg .= " -- metric values = " . $all_metrics;
          $msg .= " -- marker = " . $marker;
          $msg .= " -- function name = " . $routine;
          gp_message ("debugXL", $subr_name, $msg);

          @fields = split (" ", $input_line);

          $no_of_fields = $#fields + 1;
          $elements_in_name = $no_of_fields - $number_of_metrics - 1;

          $msg  = "no_of_fields = " . $no_of_fields;
          $msg .= " elements_in_name = " . $elements_in_name;
          gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# In case the last metric is 0. only, we append 3 extra characters that
# represent zero.  We cannot change the number to 0.000 though because that
# has a different interpretation than 0.
# In a later phase, the "ZZZ" symbol will be removed again, but for now it
# creates consistency in, for example, the length of the metrics part.
#------------------------------------------------------------------------------
              if ($all_metrics =~ /$zero_dot_at_end_regex/)
                {
                  if (defined ($1) )
                    {
#------------------------------------------------------------------------------
# Somewhat overkill, but remove the leading "\" from the decimal separator
# in the debug print since it is used for internal purposes only.
#------------------------------------------------------------------------------
                      my $decimal_point = $decimal_separator;
                      $decimal_point =~ s/$backward_slash_regex//;
                      my $txt = "all_metrics = $all_metrics ended with 0";
                      $txt   .= "$decimal_point ($decimal_separator)";
                      gp_message ("debugXL", $subr_name, $txt);

                      $all_metrics .= "ZZZ";
                    }
                }
              $metrics_length = length ($all_metrics);
              $max_metrics_length = max ($max_metrics_length, $metrics_length);
              gp_message ("debugXL", $subr_name, "$routine all_metrics = $all_metrics metrics_length = $metrics_length");

              $msg = "verify full_hex_address = " . $full_hex_address;
              gp_message ("debugXL", $subr_name, $msg);

              if ($full_hex_address =~ /$get_hex_address_regex/)
                {
                  $hex_address = "0x" . $2;
                }
              else
                {
                  $msg = "full_hex_address = $full_hex_address has the wrong format";
                  gp_message ("assertion", $subr_name, $msg);
                }

              push (@address_field, $full_hex_address);

              $msg = "pushed full_hex_address = " . $full_hex_address; 
              gp_message ("debugXL", $subr_name, $msg);

              push (@metric_values, $all_metrics);

#------------------------------------------------------------------------------
# Record the function name "as is".  Below we figure out what the final name
# should be in case there are multiple occurrences of the same name.
#
# The reason to decouple this is to avoid the code gets too complex here.
#------------------------------------------------------------------------------
              push (@function_names, $routine);
        }
    } #-- End of loop over the input lines

#------------------------------------------------------------------------------
# Store the maximum lengths for the header and metrics.
#------------------------------------------------------------------------------
    gp_message ("debugXL", $subr_name, "final max_header_length  = $max_header_length");
    gp_message ("debugXL", $subr_name, "final max_metrics_length = $max_metrics_length");

    $function_view_structure{"max header length"}  = $max_header_length;
    $function_view_structure{"max metrics length"} = $max_metrics_length;

#------------------------------------------------------------------------------
# Determine the final name for the functions and set up the HTML block.
#------------------------------------------------------------------------------
  my @final_html_function_block = ();
  my @function_index_list       = ();

#------------------------------------------------------------------------------
# First, an index list is built.  If we are to index the functions in order of
# appearance in the function overview from 0 to n-1, the value of the array
# for index "i" is the index into the large "function_info" structure.  This
# has the final name, the html function block, etc.
#------------------------------------------------------------------------------

  for my $i (keys @address_field)
    {
      $msg = "address_field[" . $i ."] = " . $address_field[$i];
      gp_message ("debugM", $subr_name, $msg);
    }
#------------------------------------------------------------------------------
## TBD: Use get_index_function_info??!!
#------------------------------------------------------------------------------
  for my $i (keys @function_names)
    {
#------------------------------------------------------------------------------
# Get the function name and the address from the function overview.  The
# address is used to differentiate in case a function has multiple occurences.
#------------------------------------------------------------------------------
      my $routine = $function_names[$i];
      my $current_address = $address_field[$i];

      my $final_function_name;
      my $found_a_match = $FALSE;
      my $msg;
      my $ref_index;

      $msg  = "on entry - routine = " . $routine; 
      $msg .= " current_address = " . $current_address;
      gp_message ("debugM", $subr_name, $msg);

#------------------------------------------------------------------------------
# Check if there are duplicate entries for this function.  If there are, use
# the address to find the right match in the function_info structure.
#------------------------------------------------------------------------------
      gp_message ("debugXL", $subr_name, "$routine: first check for multiple occurrences");
      if (exists ($g_multi_count_function{$routine}))
        {
          $msg = "$g_multi_count_function{$routine} exists";
          gp_message ("debugXL", $subr_name, $msg);
          $msg  = "g_function_occurrences{$routine} = ";
          $msg .= $g_function_occurrences{$routine};
          gp_message ("debugXL", $subr_name, $msg);

          for my $ref (keys @{ $g_map_function_to_index{$routine} })
            {
              my $ref_index = $g_map_function_to_index{$routine}[$ref];
              my $addr_offset = $function_info[$ref_index]{"addressobjtext"};
#------------------------------------------------------------------------------
# The address has the following format: 6:0x0003af50, but we only need the
# part after the colon and remove the first part.
#------------------------------------------------------------------------------
              $addr_offset =~ s/$get_addr_offset_regex//;
 
              gp_message ("debugXL", $subr_name, "$routine: ref_index = $ref_index");
              gp_message ("debugXL", $subr_name, "$routine: function_info[$ref_index]{'alt_name'} = $function_info[$ref_index]{'alt_name'}");
              gp_message ("debugXL", $subr_name, "$routine: addr_offset = $addr_offset");

              if ($addr_offset eq $current_address)
#------------------------------------------------------------------------------
# There is a match and we can store the index.
#------------------------------------------------------------------------------
                {
                  $found_a_match = $TRUE;
                  push (@function_index_list, $ref_index);
                  last;
                }
            }
        }
      else
        {
#------------------------------------------------------------------------------
# This is the easy case.  There is only one index value.  We do check if the
# array element that contains it, exists.  If this is not the case, something
# has gone horribly wrong earlier and we need to bail out.
#------------------------------------------------------------------------------
          if (defined ($g_map_function_to_index{$routine}[0]))
            {
              $found_a_match = $TRUE;
              $ref_index = $g_map_function_to_index{$routine}[0];
              push (@function_index_list, $ref_index);
              my $final_function_name = $function_info[$ref_index]{"routine"};
              gp_message ("debugXL", $subr_name, "pushed single occurrence: ref_index = $ref_index final_function_name = $final_function_name");
            }
          }
      if (not $found_a_match)
#------------------------------------------------------------------------------
# This should not happen. All we can do is print an error message and stop.
#------------------------------------------------------------------------------
        {
          $msg  = "cannot find the index for $routine: found_a_match = ";
          $msg .= ($found_a_match == $TRUE) ? "TRUE" : "FALSE";
          gp_message ("assertion", $subr_name, $msg);
        }
    }

#------------------------------------------------------------------------------
# The loop over all function names has completed and @function_index_list
# contains the index values into @function_info for the functions.
#
# All we now need to do is to retrieve the correct field(s) from the array.
#------------------------------------------------------------------------------
  for my $i (keys @function_index_list)
    {
      my $index_for_function = $function_index_list[$i];
      push (@final_html_function_block, $function_info[$index_for_function]{"html function block"});
    }
  for my $i (keys @final_html_function_block)
    {
      my $txt = "final_html_function_block[$i] = $final_html_function_block[$i]";
      gp_message ("debugXL", $subr_name, $txt);
    }

#------------------------------------------------------------------------------
# Since the numbers are right aligned, we know that any difference between the
# metric line length and the maximum must be caused by the first column.  All
# we need to do is to prepend spaces in case of a difference.
#
# While we have the line with the metric values, we also replace ZZZ by 3
# spaces.
#------------------------------------------------------------------------------
    for my $i (keys @metric_values)
      {
        if (length ($metric_values[$i]) < $max_metrics_length)
          {
            my $pad = $max_metrics_length - length ($metric_values[$i]);
            my $spaces = "";
            for my $s (1 .. $pad)
              {
                $spaces .= "&nbsp;";
              }
            $metric_values[$i] = $spaces . $metric_values[$i];
          }
          $metric_values[$i] =~ s/ZZZ/&nbsp;&nbsp;&nbsp;/g;
      }

#------------------------------------------------------------------------------
# Determine the column widths.  The start and end index of the words in the
# input line are stored in elements 0 and 1 of @word_index_values.
#
# The assumption made is that the first digit of a metric value on the first
# line is left # aligned with the header text.  These are the Total values
# and other than for some derived metrics, e.g. CPI, should be the largest.
#
# The positions of the start of the value is what we should then use for the
# word "(sort)" to start.
#
# For example:
#
# Excl.     Excl. CPU  Excl.         Excl.         Excl.  Excl.
# Total     Cycles     Instructions  Last-Level    IPC    CPI
# CPU sec.     sec.    Executed      Cache Misses
# 174.664   179.250    175838403203  1166209617    0.428   2.339
#------------------------------------------------------------------------------

    my $foundit_ref;
    my $foundit;
    my @index_values = ();
    my $index_values_ref;

#------------------------------------------------------------------------------
# Search for "Excl." in the top row.  The metric values are aligned with this
# word and we can use it to position "(sort)" in the last header line.
#
# In @index_values, we store the position(s) of "Excl." in the header line.
# If none can be found, an exception is raised because at least one should
# be there.
#
# TBD: Check if this can be done only once.
#------------------------------------------------------------------------------
    my $target_keyword = "Excl.";

    ($foundit_ref, $index_values_ref) = find_keyword_in_string (
                                          \$remaining_part_header,
                                          \$target_keyword);

    $foundit      = ${ $foundit_ref };
    @index_values = @{ $index_values_ref };

    if ($foundit)
      {
        for my $i (keys @index_values)
          {
            my $txt = "index_values[$i] = $index_values[$i]";
            gp_message ("debugXL", $subr_name, $txt);
          }
      }
    else
      {
        $msg = "keyword $target_keyword not found in $remaining_part_header";
        gp_message ("assertion", $subr_name, $msg);
      }

#------------------------------------------------------------------------------
# Compute the number of spaces we need to add between the "(sort)" strings.
#
# For example:
#
# 01234567890123456789
#
# Excl.         Excl.
# (sort)        (sort)
#       xxxxxxxx
#
# The number of spaces required is 14 - 6 = 8.
#
# The number of spaces to be added is stored in @padding_values.  These are
# the spaces to be added before the occurrence of "(sort)".  This is why the
# first padding value is 0.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# TBD: This needs to be done only once.
#------------------------------------------------------------------------------
    my @padding_values = ();
    my $P_previous     = 0;
    for my $i (keys @index_values)
      {
        my $L = $index_values[$i];
        my $P = $L + length ("(sort)");
        my $pad_spaces = $L - $P_previous;

        push (@padding_values, $pad_spaces);

        $P_previous = $P;
      }

    for my $i (keys @padding_values)
      {
        my $txt = "padding_values[$i] = $padding_values[$i]";
        gp_message ("debugXL", $subr_name, $txt);
      }
 
#------------------------------------------------------------------------------
# Build up the sort line.  Mark the current metric and make sure the line is
# aligned with the header.
#------------------------------------------------------------------------------
    my $sort_string = "(sort)";
    my $length_sort_string = length ($sort_string);
    my $sort_line = "";
    my @active_metrics = split (":", $summary_metrics);
    for my $i (0 .. $number_of_metrics-1)
      {
        my $pad          = $padding_values[$i];
        my $metric_value = $active_metrics[$i];

        my $spaces = "";
        for my $s (1 .. $pad)
          {
            $spaces .= "&nbsp;";
          }

        gp_message ("debugXL", $subr_name, "i = $i metric_value = $metric_value pad = $pad");

        if ($metric_value eq $exp_type)
#------------------------------------------------------------------------------
# The current metric should have a different background color.
#------------------------------------------------------------------------------
          {
            $sort_string = "<a href=\'" . $g_html_base_file_name{"function_view"} .
                           "." . $metric_value . ".html' style='background-color:" .
                           $g_html_color_scheme{"background_selected_sort"} .
                           "\'><b>(sort)</b></a>";
          }
        elsif (($exp_type eq "functions") and ($metric_value eq $g_first_metric))
#------------------------------------------------------------------------------
# Set the background color for the sort metric in the main function overview.
#------------------------------------------------------------------------------
          {
            $sort_string = "<a href=\'" . $g_html_base_file_name{"function_view"} .
                           "." . $metric_value . ".html' style='background-color:" .
                           $g_html_color_scheme{"background_selected_sort"} .
                           "'><b>(sort)</b></a>";
          }
        else
#------------------------------------------------------------------------------
# Do not set a specific background for all other metrics.
#------------------------------------------------------------------------------
          {
            $sort_string = "<a href=\'" . $g_html_base_file_name{"function_view"} .
                           "." . $metric_value . ".html'>(sort)</a>";
          }

#------------------------------------------------------------------------------
# Prepend the spaces to ensure correct alignment with the rest of the header.
#------------------------------------------------------------------------------
          $sort_line .= $spaces . $sort_string;
      }

    push (@header_lines, $sort_line);

#------------------------------------------------------------------------------
# Print the final results for the header and metrics.
#------------------------------------------------------------------------------
  for my $i (keys @header_lines)
    {
      gp_message ("debugXL", $subr_name, "header_lines[$i] = $header_lines[$i]");
    }
  for my $i (keys @metric_values)
    {
      gp_message ("debugXL", $subr_name, "metric_values[$i] = $metric_values[$i]");
    }

#------------------------------------------------------------------------------
# Construct the lines for the function overview.
#
# TBD: We could eliminate two structures here because metric_values and
# final_html_function_block are only copied and the result stored.
#------------------------------------------------------------------------------
   for my $i (keys @function_names)
      {
        push (@metrics_part, $metric_values[$i]);
        push (@function_view_array, $final_html_function_block[$i]);
      }

  for my $i (0 .. $#function_view_array)
    {
      $msg = "function_view_array[$i] = $function_view_array[$i]";
      gp_message ("debugXL", $subr_name, $msg);
    }
#------------------------------------------------------------------------------
# Element "function table" contains the array with all the function view data.
#------------------------------------------------------------------------------
  $function_view_structure{"header"}         = [@header_lines];
  $function_view_structure{"metrics part"}   = [@metrics_part];
  $function_view_structure{"function table"} = [@function_view_array];

  $msg = "leave subroutine " . $subr_name;
  gp_message ("debug", $subr_name, $msg);

  return (\%function_view_structure);

} #-- End of subroutine process_function_overview

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub process_metrics
{
  my $subr_name = get_my_name ();

  my ($input_string, $sort_fields_ref, $metric_description_ref, $ignored_metrics_ref) = @_;

  my @sort_fields        = @{ $sort_fields_ref };
  my %metric_description = %{ $metric_description_ref };
  my %ignored_metrics    = %{ $ignored_metrics_ref };

  my $outputdir = append_forward_slash ($input_string);
  my $LANG      = $g_locale_settings{"LANG"};
  my $max_len   = 0;
  my $metric_comment;

  my ($imetricn,$outfile);
  my ($html_metrics_record,$imetric,$metric);

  $html_metrics_record =
    "<!doctype html public \"-//w3c//dtd html 3.2//EN\">\n<html lang=\"$LANG\">\n<head>\n" .
    "<meta http-equiv=\"content-type\" content=\"text/html; charset=iso-8859-1\">\n" .
    "<title>Function Metrics</title></head><body lang=\"$LANG\" bgcolor=".$g_html_color_scheme{"background_color_page"}."<pre>\n";

  $outfile = $outputdir . "metrics.html";

  open (METRICSOUT, ">", $outfile)
    or die ("$subr_name - unable to open file $outfile for writing - '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile for writing");

  for $metric (@sort_fields)
    {
      $max_len = max ($max_len, length ($metric));
      gp_message ("debug", $subr_name, "sort_fields: metric = $metric max_len = $max_len");
    }

# TBD: Check this
#  for $imetric (@IMETRICS)
  for $imetric (keys %ignored_metrics)
    {
      $max_len = max ($max_len, length ($imetric));
      gp_message ("debug", $subr_name, "ignored_metrics imetric = $imetric max_len = $max_len");
    }

  $max_len++;

  gp_message ("debug", $subr_name, "max_len = $max_len");

  $html_metrics_record .= "<p style=\"font-size:14px;color:red\"> Metrics used (".($#sort_fields + 1).")\n</p><p style=\"font-size:14px\">";
  for $metric (@sort_fields)
    {
      my $description = ${ retrieve_metric_description (\$metric, \%metric_description) };
      gp_message ("debug", $subr_name, "handling metric metric = $metric->$description");
      $html_metrics_record .= "       $metric".(' ' x ($max_len - length ($metric)))."$description\n";
    }

#  $imetricn = scalar (keys %IMETRICS);
  $imetricn = scalar (keys %ignored_metrics);
  if ($imetricn)
    {
      $html_metrics_record .= "</p><p style=\"font-size:14px;color:red\"> Metrics ignored ($imetricn)\n</p><p style=\"font-size:14px\">";
#      for $imetric (sort keys %IMETRICS){
      for $imetric (sort keys %ignored_metrics)
        {
              $metric_comment = "(inclusive, exclusive, and percentages)";
          $html_metrics_record .= "       $imetric".(' ' x ($max_len - length ($imetric))).$metric_comment."\n";
          gp_message ("debug", $subr_name, "handling metric imetric = $imetric $metric_comment");
        }
    }

  print METRICSOUT $html_metrics_record;
  print METRICSOUT $g_html_credits_line;
  close (METRICSOUT);

  gp_message ("debug", $subr_name, "closed metrics file $outfile");

  return (0);

} #-- End of subroutine process_metrics

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub process_metrics_data
{
  my $subr_name = get_my_name ();

  my ($outfile1, $outfile2, $ignored_metrics_ref) = @_;

  my %ignored_metrics    = %{ $ignored_metrics_ref };

  my %metric_value       = ();
  my %metric_description = ();
  my %metric_found       = ();

  my $user_metrics;
  my $system_metrics;
  my $wall_metrics;
  my $metric_spec;
  my $metric_flavor;
  my $metric_visibility;
  my $metric_name;
  my $metric_text;
  my $metricdata;
  my $metric_line;
  my $msg;

  my $summary_metrics;
  my $detail_metrics;
  my $detail_metrics_system;
  my $call_metrics;

#------------------------------------------------------------------------------
# The regex section.
#------------------------------------------------------------------------------
  my $metrics_line_regex         = '\s*(.*):\s+(\d+\.?\d*)';
  my $metric_of_interest_1_regex = '^Exclusive\ *';
  my $metric_of_interest_2_regex = '^Inclusive\ *';

  if ($g_user_settings{"default_metrics"}{"current_value"} eq "off")
    {
      $msg  = "g_user_settings{default_metrics}{current_value} = ";
      $msg .= $g_user_settings{"default_metrics"}{"current_value"};
      gp_message ("debug", $subr_name, $msg);
  # get metrics

      $summary_metrics       = '';
      $detail_metrics        = '';
      $detail_metrics_system = '';
      $call_metrics          = '';
      $user_metrics          = 0;
      $system_metrics        = 0;
      $wall_metrics          = 0;

      my ($last_metric,$metric,$value,$i,$r);

      open (METRICTOTALS, "<", $outfile2)
        or die ("Unable to open metric value data file $outfile2 for reading - '$!'");
      gp_message ("debug", $subr_name, "opened $outfile2 to parse metric value data");

#------------------------------------------------------------------------------
# Below an example of the file that has just been opened.
#------------------------------------------------------------------------------
# <Total>
# 	            Exclusive Total CPU Time:      3.232 (100.0%)
# 	            Inclusive Total CPU Time:      3.232 (100.0%)
# 	              Exclusive insts Events: 7628146366 (100.0%)
# 	              Inclusive insts Events: 7628146366 (100.0%)
# 	             Exclusive cycles Events: 5167454376 (100.0%)
# 	             Inclusive cycles Events: 5167454376 (100.0%)
# 	   Exclusive dTLB-load-misses Events:          0 (  0. %)
# 	   Inclusive dTLB-load-misses Events:          0 (  0. %)
# 	    Exclusive Instructions Per Cycle:      1.476
# 	    Inclusive Instructions Per Cycle:      1.476
# 	    Exclusive Cycles Per Instruction:      0.677
# 	    Inclusive Cycles Per Instruction:      0.677
# 	Exclusive branch-instructions Events: 1268741580 (100.0%)
# 	Inclusive branch-instructions Events: 1268741580 (100.0%)
# 	                                Size:          0
# 	                          PC Address: 1:0x00000000
# 	                         Source File: (unknown)
# 	                         Object File: (unknown)
# 	                         Load Object: <Total>
# 	                        Mangled Name:
# 	                             Aliases:
#------------------------------------------------------------------------------

      while (<METRICTOTALS>)
        {
          $metricdata = $_; chomp ($metricdata);

          $msg = "file metrictotals: input line = " . $metricdata;
          gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Ignoring whitespace, search for any line with a ":" in it, followed by
# a number with, or without, a dot.  So, an integer or floating-point number.
#------------------------------------------------------------------------------
          if ($metricdata =~ /$metrics_line_regex/) 
            {
              $msg = "selected input line for processing"; 
              gp_message ("debug", $subr_name, $msg);

              if (defined($1) and defined($2))
                {
                  $metric = $1;
                  $value  = $2;
                  $msg = "metric = " . $metric; 
                  gp_message ("debug", $subr_name, $msg);
                  $msg = "value  = " . $value;
                  gp_message ("debug", $subr_name, $msg);
                }
              else
                {
                  $msg = "unexpected input in " . $metricdata;
                  gp_message ("assertion", $subr_name, $msg);
                }

#------------------------------------------------------------------------------
# Select the metrics of interest.
#------------------------------------------------------------------------------
              if (($metric =~ /$metric_of_interest_1_regex/) or
                  ($metric =~ /$metric_of_interest_2_regex/) )
                {
                  $msg  = "metric of interest = " . $metric;
                  $msg .= " - proceed with processing";
                  gp_message ("debug", $subr_name, $msg);
                }
              else
                {
                  $msg  = "metric = " . $metric;
                  $msg .= " - ignored and further processing is skipped";
                  gp_message ("debug", $subr_name, $msg);
                  next;
                }

#------------------------------------------------------------------------------
# When we get here, it means that this is a metric we want to process.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# TBD - Still needed? Don't see it in the input anymore (?)
#------------------------------------------------------------------------------
              if ($metric eq '" count')
#------------------------------------------------------------------------------
# Hardware counter experiments have this info.  Note that this line is not the
# first one to be encountered, so $last_metric has been defined already.
#------------------------------------------------------------------------------
                {
                  $metric = $last_metric . " Count";
                  $msg = "last_metric = $last_metric metric = $metric";
                  gp_message ("debug", $subr_name, $msg);
                }

              $metric_value{$metric} = $value;
              $msg = "archived: metric_value{$metric} = " .
                     $metric_value{$metric};
              gp_message ("debug", $subr_name, $msg);
#------------------------------------------------------------------------------
# Preserve the current metric.
#------------------------------------------------------------------------------
              $last_metric = $metric;
            }
        }
      close (METRICTOTALS);
    }

    if (scalar (keys %metric_value) == 0)
#------------------------------------------------------------------------------
# This means that there are no metrics in the input file.  That is a fatal
# error and execution is terminated.
#------------------------------------------------------------------------------
      {
        $msg = "no metrics have been found in the input file";
        gp_message ("assertion", $subr_name, $msg);
      }
    else
#------------------------------------------------------------------------------
# All is well.  Print the metrics that have been found.
#------------------------------------------------------------------------------
      {
        $msg = "stored the following metrics and values:";
        gp_message ("debug", $subr_name, $msg);
        for my $metric (sort keys %metric_value)
          {
            $msg = "metric_value{$metric} = " . $metric_value{$metric};
            gp_message ("debug", $subr_name, $msg);
          }
      }

  gp_message ("debug", $subr_name, "proceed to process file $outfile1");

#------------------------------------------------------------------------------
# Open and process the metrics file.
#------------------------------------------------------------------------------
  open (METRICS, "<", $outfile1)
    or die ("Unable to open metrics file $outfile1: '$!'");
  gp_message ("debug", $subr_name, "opened file $outfile1 for reading");

#------------------------------------------------------------------------------
# Parse the file.  This is a typical example:
#
# Exp Sel Total
# === === =====
#   1 all     2
#   2 all     1
#   3 all     2
# Current metrics: e.totalcpu:i.totalcpu:e.cycles:e+insts:e+llm:name
# Current Sort Metric: Exclusive Total CPU Time ( e.totalcpu )
# Available metrics:
#          Exclusive Total CPU Time: e.%totalcpu
#          Inclusive Total CPU Time: i.%totalcpu
#              Exclusive CPU Cycles: e.+%cycles
#              Inclusive CPU Cycles: i.+%cycles
#   Exclusive Instructions Executed: e+%insts
#   Inclusive Instructions Executed: i+%insts
# Exclusive Last-Level Cache Misses: e+%llm
# Inclusive Last-Level Cache Misses: i+%llm
#  Exclusive Instructions Per Cycle: e+IPC
#  Inclusive Instructions Per Cycle: i+IPC
#  Exclusive Cycles Per Instruction: e+CPI
#  Inclusive Cycles Per Instruction: i+CPI
#                              Size: size
#                        PC Address: address
#                              Name: name
#------------------------------------------------------------------------------
  while (<METRICS>)
    {
      $metric_line = $_;
      chomp ($metric_line);

      gp_message ("debug", $subr_name, "processing line: $metric_line");
#------------------------------------------------------------------------------
# The original regex has bugs because the line should not be allowed to start
# with a ":".  So this is wrong:
#  if (($metric =~ /\s*(.*):\s+(\S)((\.\S+)|(\+\S+))/) and !($metric =~/^Current/))
#
# This is better:
#      if (($metric =~ /\s*(.+):\s+(\S)((\.\S+)|(\+\S+))/) and !($metric =~/^Current/))
#
# In general, this regex has some potential issues and has been replaced by
# the one shown below.
#
# We select a line that does not start with "Current" and aside from whitespace
# starts with anything (although it should be a string with words only),
# followed by whitespace and either an "e" or "i". This is called the "flavor"
# and is followed by a visibility marker (.,+,%, or !) and a metric name.
#------------------------------------------------------------------------------
# Ruud   if (($metric =~ /\s*(.*):\s+(\S)((\.\S+)|(\+\S+))/) && !($metric =~/^Current/)){

      ($metric_spec, $metric_flavor, $metric_visibility, $metric_name,
       $metric_text) =
              extract_metric_specifics ($metric_line);

#      if (($metric_line =~ /\s*(.+):\s+([ei])([\.\+%]+)(\S*)/) and !($metric_line =~/^Current/))
      if ($metric_spec eq "skipped")
        {
          $msg = "skipped processing line: " . $metric_line;
          gp_message ("debug", $subr_name, $msg);
          next
        }
      $msg = "line of interest: " . $metric_line;
      gp_message ("debug", $subr_name, $msg);

      $metric_found{$metric_spec} = $TRUE;

#------------------------------------------------------------------------------
# TBD
# Currently always FALSE since this feature has not been fully implemented yet.
#------------------------------------------------------------------------------
      if ($g_user_settings{"ignore_metrics"}{"defined"})
        {
          gp_message ("debug", $subr_name, "check for $metric_spec");
          if (exists ($ignored_metrics{$metric_name}))
            {
              $msg = "user asked to ignore metric " . $metric_name;
              gp_message ("debug", $subr_name, $msg);
              $msg = "further processing of line of interest is skipped";
              gp_message ("debug", $subr_name, $msg);
              next;
            }
        }

#------------------------------------------------------------------------------
# This metric is not on the ignored list and qualifies, so store it.
#------------------------------------------------------------------------------
      $metric_description{$metric_spec} = $metric_text;

# TBD: add for other visibilities too, like +
      $msg  = "stored metric_description{$metric_spec} = ";
      $msg .= $metric_description{$metric_spec};
      gp_message ("debug", $subr_name, $msg);

      if ($metric_flavor ne "e")
        {
          $msg = "metric $metric_spec is ignored";
          gp_message ("debug", $subr_name, $msg);
          $msg = "further processing of this line is skipped";
          gp_message ("debug", $subr_name, $msg);
        }
      else
#------------------------------------------------------------------------------
# Only the exclusive metrics are shown.
#------------------------------------------------------------------------------
        {
          $msg = "metric $metric_spec ($metric_text) is considered";
          gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Legacy metrics, but may re-appear one day and so the code is left in here.
#------------------------------------------------------------------------------
          if ($metric_spec =~ /user/)
            {
              $user_metrics = $TRUE;
              $msg = "user_metrics set to TRUE";
              gp_message ("debug", $subr_name, $msg);
            }
          elsif ($metric_spec =~ /system/)
            {
              $system_metrics = $TRUE;
              $msg = "system_metrics set to TRUE";
              gp_message ("debug", $subr_name, $msg);
            }
          elsif ($metric_spec =~ /wall/)
            {
              $wall_metrics = $TRUE;
              $msg = "wall_metrics set to TRUE";
              gp_message ("debug", $subr_name, $msg);
            }
          elsif (defined ($metric_value{$metric_text}))
            {
              $msg  = "total attributed to this metric ";
              $msg .= "metric_value{" . $metric_text . "} = ";
              $msg .= $metric_value{$metric_text};
              gp_message ("debug", $subr_name, $msg);

              if ($summary_metrics ne '')
                {
                  $summary_metrics .= ':' . $metric_spec;
                  $msg = "updated summary_metrics = " . $summary_metrics;
                  gp_message ("debug", $subr_name, $msg);
                }
              else
                {
                  $summary_metrics = $metric_spec;
                  $msg = "initialized summary_metrics = " . $summary_metrics;
                  gp_message ("debug", $subr_name, $msg);
                }
              gp_message ("debug", $subr_name, "metric $metric_spec added");
            }
          else
            {
#------------------------------------------------------------------------------
# TBD: This doesn't seem to make much sense.
#------------------------------------------------------------------------------
              $msg = "no action taken for " . $metric_spec;
              gp_message ("debug", $subr_name, $msg);
            }
        }
    }

  close METRICS;

  if ($wall_metrics > 0)
    {
      $msg = "adding e.wall to summary_metrics";
      gp_message ("debug", $subr_name, $msg);
      $summary_metrics = "e.wall:".$summary_metrics;
      $msg = "after update summary_metrics = " . $summary_metrics;
      gp_message ("debug", $subr_name, $msg);
    }

  if ($system_metrics > 0)
    {
      $msg = "adding e.system to summary_metrics and detail_metrics_system";
      gp_message ("debug", $subr_name, $msg);

      $summary_metrics        = "e.system:" . $summary_metrics;
      $detail_metrics_system  = "e.system:" . $detail_metrics_system;

      $msg = "adding i.system to call_metrics";
      gp_message ("debug", $subr_name, $msg);

      $call_metrics = "i.system:" . $call_metrics;

      $msg = "after update summary_metrics       = " . $summary_metrics;
      gp_message ("debug", $subr_name, $msg);
      $msg = "after update call_metrics          = " . $call_metrics;
      gp_message ("debug", $subr_name, $msg);
      $msg = "after update detail_metrics_system = " . $detail_metrics_system;
      gp_message ("debug", $subr_name, $msg);
    }


#------------------------------------------------------------------------------
# TBD: e.user and i.user do not always exist!!
#------------------------------------------------------------------------------

  if ($user_metrics > 0)
    {
# Ruud      if (!exists ($IMETRICS{"i.user"})){
      if ($g_user_settings{"ignore_metrics"}{"defined"} and exists ($ignored_metrics{"user"}))
        {
          $summary_metrics = "e.user:".$summary_metrics;
        }
      else
        {
          $summary_metrics = "e.user:i.user:".$summary_metrics;
        }

      $detail_metrics        = "e.user:".$detail_metrics;
      $detail_metrics_system = "e.user:".$detail_metrics_system;

      if ($g_user_settings{"ignore_metrics"}{"defined"} and
          exists ($ignored_metrics{"user"}))
        {
          $call_metrics = "a.user:".$call_metrics;
        }
      else
        {
          $call_metrics = "a.user:i.user:".$call_metrics;
        }
      $msg = "updated summary_metrics = " . $summary_metrics;
      gp_message ("debug", $subr_name, $msg);
      $msg = "updated detail_metrics        = " . $detail_metrics;
      gp_message ("debug", $subr_name, $msg);
      $msg = "updated detail_metrics_system = " . $detail_metrics_system;
      gp_message ("debug", $subr_name, $msg);
      $msg = "updated call_metrics          = " . $call_metrics;
      gp_message ("debug", $subr_name, $msg);

    }

#------------------------------------------------------------------------------
# TBD
# It doesn't look right in case call_metrics ends up being set to ""
#------------------------------------------------------------------------------
  if ($call_metrics eq "")
    {
      $call_metrics = $detail_metrics;
      $msg = "call_metrics is not set, setting it to " . $call_metrics;
      gp_message ("debug", $subr_name, $msg);
      if ($detail_metrics eq '')
        {
          $msg  = "detail_metrics and call_metrics are blank and could";
          $msg .= " cause trouble later on";
          gp_message ("debug", $subr_name, $msg);
        }
    }

  for my $metric (sort keys %ignored_metrics)
    {
      if ($ignored_metrics{$metric})
        {
          $msg = "active metric, but ignored: " . $metric;
          gp_message ("debug", $subr_name, $msg);
        }

    }

  return (\%metric_value, \%metric_description, \%metric_found, $user_metrics,
          $system_metrics, $wall_metrics, $summary_metrics, $detail_metrics,
          $detail_metrics_system, $call_metrics);

} #-- End of subroutine process_metrics_data

#------------------------------------------------------------------------------
# Process source lines that are not part of the target function.
#
# Generate straightforward HTML, but define an anchor based on the source line
# number in the list.
#------------------------------------------------------------------------------
sub process_non_target_source
{
  my $subr_name = get_my_name ();

  my ($start_scan, $end_scan,
      $src_times_regex, $function_regex, $number_of_metrics,
      $file_contents_ref, $modified_html_ref) = @_;

  my @file_contents = @{ $file_contents_ref };
  my @modified_html = @{ $modified_html_ref };
  my $colour_code_line = $FALSE;
  my $input_line;
  my $line_id;
  my $modified_line;

#------------------------------------------------------------------------------
# Main loop to parse all of the source code and take action as needed.
#------------------------------------------------------------------------------
  for (my $line_no=$start_scan; $line_no <= $end_scan; $line_no++)
    {
      $input_line = $file_contents[$line_no];

#------------------------------------------------------------------------------
# Generate straightforward HTML, but define an anchor based on the source line
# number in the list.
#------------------------------------------------------------------------------
      $line_id = extract_source_line_number ($src_times_regex,
                                             $function_regex,
                                             $number_of_metrics,
                                             $input_line);

      if ($input_line =~ /$function_regex/)
        {
          $colour_code_line = $TRUE;
        }

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
      $input_line =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

#------------------------------------------------------------------------------
# Add an id.
#------------------------------------------------------------------------------
      $modified_line = "<a id=\"line_" . $line_id . "\"></a>";

      my $coloured_line;
      if ($colour_code_line)
        {
          my $boldface = $TRUE;
          $coloured_line = color_string (
                             $input_line,
                             $boldface,
                             $g_html_color_scheme{"non_target_function_name"});
          $colour_code_line = $FALSE;
          $modified_line .= "$coloured_line";
        }
      else
        {
          $modified_line .= "$input_line";
        }
      gp_message ("debugXL", $subr_name, " $line_no : modified_line = $modified_line");
      push (@modified_html, $modified_line);
    }

  return (\@modified_html);

} #-- End of subroutine process_non_target_source

#------------------------------------------------------------------------------
# This function scans the configuration file and adapts the internal settings
# accordingly.
#
# Errors are stored during the parsing and processing phase.  They are printed
# at the end and sorted by line number.
#
#
# TBD: Does not yet use the warnings/error system.  This needs to be fixed.
#------------------------------------------------------------------------------
sub process_rc_file
{
  my $subr_name = get_my_name ();

  my ($rc_file_name, $rc_file_paths_ref) = @_;

#------------------------------------------------------------------------------
# Local structures.
#------------------------------------------------------------------------------
# Stores the values extracted from the config file:
  my %rc_settings_user = ();
  my %error_and_warning_msgs = ();
  my @rc_file_paths = ();

  my @split_line;
  my @my_fields;

  my $msg;
  my $first_part;
  my $line;
  my $line_number;
  my $no_of_arguments;
  my $number_of_fields;
  my $number_of_paths;
  my $parse_errors;   #-- Count the number of errors
  my $parse_warnings; #-- Count the number of errors

  my $rc_config_file;
  my $rc_file_found;
  my $rc_keyword;
  my $rc_value;

  @rc_file_paths   = @{$rc_file_paths_ref};
  $number_of_paths = scalar (@rc_file_paths);

  if ($number_of_paths == 0)
#------------------------------------------------------------------------------
# This should not happen, but is a good safety net to add.
#------------------------------------------------------------------------------
    {
      my $msg = "search path list is empty";
      gp_message ("assertion", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Check for the presence of a configuration file.
#------------------------------------------------------------------------------
  $msg = "number_of_paths = $number_of_paths rc_file_paths = @rc_file_paths";
  gp_message ("debug", $subr_name, $msg);

  $rc_file_found = $FALSE;
  for my $path_name (@rc_file_paths)
    {
      $rc_config_file = $path_name . "/" . $rc_file_name;
      $msg = "looking for configuration file " . $rc_config_file;
      gp_message ("debug", $subr_name, $msg);
      if (-f $rc_config_file)
        {
          $msg = "found configuration file " . $rc_config_file;
          gp_message ("debug", $subr_name, $msg);
          $rc_file_found  = $TRUE;
          last;
        }
    }

  if (not $rc_file_found)
#------------------------------------------------------------------------------
# There is no configuration file and we can skip this subroutine.
#------------------------------------------------------------------------------
    {
      $msg = "configuration file $rc_file_name not found";
      gp_message ("verbose", $subr_name, $msg);
      return (0);
    }
  else
    {
      $msg = "unable to open file $rc_config_file for reading:";
      open (GP_DISPLAY_HTML_RC, "<", "$rc_config_file")
        or die ($subr_name . " - " . $msg . " " . $!);
#------------------------------------------------------------------------------
# The configuration file has been opened for reading.
#------------------------------------------------------------------------------
      $msg = "file $rc_config_file has been opened for reading";
      gp_message ("debug", $subr_name, $msg);
    }

  $msg = "found configuration file $rc_config_file";
  gp_message ("verbose", $subr_name, $msg);
  $msg = "processing configuration file " . $rc_config_file;
  gp_message ("debug", $subr_name, $msg);

#------------------------------------------------------------------------------
# Here we scan the configuration file for the settings.
#
# A setting consists of a keyword, optionally followed by a value.  It is
# optional because not all keywords may require a value.
#
# At the end of this block, all keyword/value pairs are stored in a hash.
#
# We do not yet check for the validity of these pairs. This is done next.
#
# The original code had this all integrated, but it made the code very
# complex with deeply nested if-statements. The flow was also hard to follow.
#------------------------------------------------------------------------------
  $parse_errors   = 0;
  $parse_warnings = 0;
  $line_number    = 0;
  while (my $line = <GP_DISPLAY_HTML_RC>)
    {
      chomp ($line);
      $line_number++;

      gp_message ("debug", $subr_name, "read input line = $line");

#------------------------------------------------------------------------------
# Ignore a line with whitespace only
#------------------------------------------------------------------------------
      if ($line =~ /^\s*$/)
        {
          gp_message ("debug", $subr_name, "ignored a line with whitespace");
          next;
        }

#------------------------------------------------------------------------------
# Ignore a comment line, defined by starting with a "#", possibly prepended by
# whitespace.
#------------------------------------------------------------------------------
      if ($line =~ /^\s*\#/)
        {
          gp_message ("debug", $subr_name, "ignored a full comment line");
          next;
        }

#------------------------------------------------------------------------------
# Split the input line using the "#" symbol as a separator.  We have already
# handled the case of an isolated comment line, so there may only be an
# embedded comment.
#
# Regardless of this, we are only interested in the first part.
#------------------------------------------------------------------------------
      @split_line = split ("#", $line);

      for my $i (@split_line)
        {
          gp_message ("debug", $subr_name, "elements after split of line: $i");
        }

      $first_part = $split_line[0];
      gp_message ("debug", $subr_name, "relevant part = $first_part");

      if ($first_part =~ /[&\^\*\@\$]+/)
#------------------------------------------------------------------------------
# The &, ^, *, @ and $ symbols should not occur.  If they do, we flag an error
# an fetch the next line.
#------------------------------------------------------------------------------
        {
          $parse_errors++;
          $msg = "non-supported character(s) (\&,\^,\*,\@,\$) found: $line";
          $error_and_warning_msgs{"error"}{$line_number}{"message"} = $msg;
          next;
        }
      else
#------------------------------------------------------------------------------
# Split the first part on whitespace and verify the number of fields to be
# valid.  Although we currently only have keywords with a value, a keyword
# without value is supported to.
#
# If the number of fields is valid, the keyword and value are stored.  In case
# of a single field, the value is assigned a special string.
#
# Although this situation should not occur, we do abort if something unexpected
# is encountered here.
#------------------------------------------------------------------------------
        {
          @my_fields = split (/\s/, $split_line[0]);

          $number_of_fields = scalar (@my_fields);
          $msg = "number of fields = " . $number_of_fields;
          gp_message ("debug", $subr_name, $msg);
        }

      if ($number_of_fields ge 3)
#------------------------------------------------------------------------------
# This is not supported.
#------------------------------------------------------------------------------
        {
          $parse_errors++;
          $msg = "more than 2 fields found: $first_part";
          $error_and_warning_msgs{"error"}{$line_number}{"message"} = $msg;
          next;
        }
      elsif ($number_of_fields eq 2)
        {
          $rc_keyword = $my_fields[0];
          $rc_value   = $my_fields[1];
        }
      elsif ($number_of_fields eq 1)
        {
          $rc_keyword = $my_fields[0];
          $rc_value   = "the_field_is_empty";
        }
      else
        {
          $msg  = "[line $line_number] $rc_config_file -";
          $msg .= " number of fields = $number_of_fields";
          gp_message ("assertion", $subr_name, $msg);
        }

#------------------------------------------------------------------------------
# Store the keyword, value and line number.
#------------------------------------------------------------------------------
      if (exists ($rc_settings_user{$rc_keyword}))
        {
          $parse_warnings++;
          my $prev_value = $rc_settings_user{$rc_keyword}{"value"};
          my $prev_line_number = $rc_settings_user{$rc_keyword}{"line_no"};
          if ($rc_value ne $prev_value)
            {
              $msg  = "option $rc_keyword previously set at line";
              $msg .= " $prev_line_number: new value '$rc_value'";
              $msg .= " ' overrides '$prev_value'";
            }
          else
            {
              $msg  = "option $rc_keyword previously set to the same value";
              $msg .= " at line $prev_line_number";
            }
          $error_and_warning_msgs{"warning"}{$line_number}{"message"} = $msg;
        }
      $rc_settings_user{$rc_keyword}{"value"}   = $rc_value;
      $rc_settings_user{$rc_keyword}{"line_no"} = $line_number;

      gp_message ("debug", $subr_name, "stored keyword     = $rc_keyword");
      gp_message ("debug", $subr_name, "stored value       = $rc_value");
      gp_message ("debug", $subr_name, "stored line number = $line_number");
    }

#------------------------------------------------------------------------------
# Completed the parsing of the configuration file. It can be closed.
#------------------------------------------------------------------------------
  close (GP_DISPLAY_HTML_RC);

#------------------------------------------------------------------------------
# Print the raw input as just collected from the configuration file.
#------------------------------------------------------------------------------
  gp_message ("debug", $subr_name, "contents of %rc_settings_user:");
  for my $keyword (keys %rc_settings_user)
    {
      my $key_value = $rc_settings_user{$keyword}{"value"};
      $msg = "keyword = " . $keyword . " value = " . $key_value;
      gp_message ("debug", $subr_name, $msg);
    }

  for my $rc_keyword  (keys %g_user_settings)
    {
       for my $fields (keys %{ $g_user_settings{$rc_keyword} })
         {
           $msg  = "before config file: $rc_keyword $fields =";
           $msg .= " " . $g_user_settings{$rc_keyword}{$fields};
           gp_message ("debug", $subr_name, $msg);
         }
    }

#------------------------------------------------------------------------------
# We are almost done.  Check for all keywords found whether they are valid.
# Also verify that the corresponding value is valid.
#
# Update the g_user_settings table if everything is okay.
#------------------------------------------------------------------------------

  for my $rc_keyword (keys %rc_settings_user)
    {
      my $rc_value = $rc_settings_user{$rc_keyword}{"value"};

      if (exists ( $g_user_settings{$rc_keyword}))
        {

#------------------------------------------------------------------------------
# This is a supported keyword.  There are two more things left to do:
# - Check how many values it requires (currently exactly one is supported)
# - Is the value a valid number or string?
#------------------------------------------------------------------------------
          $no_of_arguments = $g_user_settings{$rc_keyword}{"no_of_arguments"};

          if ($no_of_arguments eq 1)
            {
              my $input_value = $rc_value;
              if ($input_value ne "the_field_is_empty")
#
#------------------------------------------------------------------------------
# So far, so good.  We only need to check if the value is valid for the keyword.
#------------------------------------------------------------------------------
                {
                  my $data_type   = $g_user_settings{$rc_keyword}{"data_type"};
                  my $valid_input =
			verify_if_input_is_valid ($input_value, $data_type);
#------------------------------------------------------------------------------
# Check if the value is valid.
#------------------------------------------------------------------------------
                  if ($valid_input)
                    {
                      $g_user_settings{$rc_keyword}{"current_value"} =
								$rc_value;
                      $g_user_settings{$rc_keyword}{"defined"}  = $TRUE;
                    }
                  else
                    {
                      $parse_errors++;
                      $line_number = $rc_settings_user{$rc_keyword}{"line_no"};
                      $msg  = "input value '$input_value' for keyword";
                      $msg .= " $rc_keyword is not valid";
                      $error_and_warning_msgs{"error"}{$line_number}{"message"}
								= $msg;
                      next;
                    }
                }
              else
#------------------------------------------------------------------------------
# This keyword requires a value, but none has been found.
#------------------------------------------------------------------------------
                {
                  $parse_errors++;
                  $line_number = $rc_settings_user{$rc_keyword}{"line_no"};
                  $msg = "missing value for keyword '$rc_keyword'";
                  $error_and_warning_msgs{"error"}{$line_number}{"message"}
								= $msg;
                  next;
                }
            }
          elsif ($no_of_arguments eq 0)
#------------------------------------------------------------------------------
# Currently a theoretical scenario since all commands require a value, but in
# case this is no longer true, we need to at least flag the fact the user set
# this command.
#------------------------------------------------------------------------------
            {
              $g_user_settings{$rc_keyword}{"defined"}  = $TRUE;
            }
          else
#------------------------------------------------------------------------------
# The code is not prepared for the situation one command has multiple values,
# but this situation should never occur. Still it won't hurt to add a check.
#------------------------------------------------------------------------------
            {
               my $msg = "cannot handle $no_of_arguments in the input";
               gp_message ("assertion", $subr_name, $msg);
            }
        }
      else
#------------------------------------------------------------------------------
# A non-valid keyword is found. This is flagged as an error.
#------------------------------------------------------------------------------
        {
          $parse_errors++;
          $line_number = $rc_settings_user{$rc_keyword}{"line_no"};
          $msg = "keyword $rc_keyword is not supported";
          $error_and_warning_msgs{"error"}{$line_number}{"message"} = $msg;
        }
    }
  for my $rc_keyword  (keys %g_user_settings)
    {
       for my $fields (keys %{ $g_user_settings{$rc_keyword} })
         {
           $msg  = "after config file: $rc_keyword $fields =";
           $msg .= " " . $g_user_settings{$rc_keyword}{$fields};
           gp_message ("debug", $subr_name, $msg);
         }
    }
  print_table_user_settings ("debug", "upon the return from $subr_name");

  if ( ($parse_errors == 0) and ($parse_warnings == 0) )
    {
      $msg = "successfully parsed and processed the configuration file";
      gp_message ("verbose", $subr_name, $msg);
    }
  else
    {
      if ($parse_errors > 0)
        {
          my $plural_or_single = ($parse_errors > 1) ? "errors" : "error";
          $msg  = $g_error_keyword . "found $parse_errors fatal";
          $msg .= " " .  $plural_or_single . " in the configuration file:";
          gp_message ("debug", $subr_name, $msg);
#------------------------------------------------------------------------------
# Sort the hash keys, the line numbers, alphabetically and print the
# corresponding error messages.
#------------------------------------------------------------------------------
          for my $line_no (sort {$a <=> $b}
				(keys %{ $error_and_warning_msgs{"error"} }))
            {
              $msg  = $g_error_keyword . "[line $line_no] in file";
              $msg .=  $rc_config_file . " - ";
              $msg .= $error_and_warning_msgs{"error"}{$line_no}{"message"};
              gp_message ("debug", $subr_name, $msg);
            }
        }

      if (not $g_quiet)
        {
          if ($parse_warnings > 0)
            {
              $msg  = $g_warn_keyword . " found $parse_warnings warnings in";
              $msg .= "  the configuration file:";
              gp_message ("debug", $subr_name, $msg);
              for my $line_no (sort {$a <=> $b}
				(keys %{ $error_and_warning_msgs{"warning"} }))
                {
                  $msg  = $g_warn_keyword;
                  $msg .= " [line $line_no] in file $rc_config_file - ";
                  $msg .= $error_and_warning_msgs{"warning"}{$line_no}{"message"};
                  gp_message ("debug", $subr_name, $msg);
                }
            }
        }
    }

  return ($parse_errors);

} #-- End of subroutine process_rc_file

#------------------------------------------------------------------------------
# Generate the annotated html file for the source listing.
#------------------------------------------------------------------------------
sub process_source
{
  my $subr_name = get_my_name ();

  my ($number_of_metrics, $function_info_ref,
      $outputdir, $input_filename) = @_;

  my @function_info = @{ $function_info_ref };

#------------------------------------------------------------------------------
# The regex section
#------------------------------------------------------------------------------
  my $end_src1_header_regex = '(^\s+)(\d+)\.\s+(.*)';
  my $end_src2_header_regex = '(^\s+)(<Function: )(.*)>';
  my $function_regex        = '^(\s*)<Function:\s(.*)>';
  my $function2_regex       = '^(\s*)&lt;Function:\s(.*)>';
  my $src_regex             = '(\s*)(\d+)\.(.*)';
  my $txt_ext_regex         = '\.txt$';
  my $src_filename_id_regex = '^file\.(\d+)\.src\.txt$';
  my $integer_only_regex    = '\d+';
#------------------------------------------------------------------------------
# Computed dynamically below.
# TBD: Try to move this up.
#------------------------------------------------------------------------------
  my $src_times_regex;
  my $hot_lines_regex;
  my $metric_regex;
  my $metric_extra_regex;

  my @components = ();
  my @fields_in_line = ();
  my @file_contents = ();
  my @hot_source_lines  = ();
  my @max_metric_values = ();
  my @modified_html = ();
  my @transposed_hot_lines = ();

  my $colour_coded_line;
  my $colour_coded_line_ref;
  my $line_id;
  my $ignore_value;
  my $func_name_in_src_file;
  my $html_new_line = "<br>";
  my $input_line;
  my $metric_values;
  my $modified_html_ref;
  my $modified_line;
  my $is_empty;
  my $start_all_source;
  my $start_target_source;
  my $end_target_source;
  my $output_line;
  my $hot_line;
  my $src_line_no;
  my $src_code_line;

  my $decimal_separator = $g_locale_settings{"decimal_separator"};
  my $hp_value = $g_user_settings{"highlight_percentage"}{"current_value"};

  my $file_title;
  my $found_target;
  my $html_dis_record;
  my $html_end;
  my $html_header;
  my $html_home;
  my $rounded_percentage;
  my $start_tracking;
  my $threshold_line;

  my $base;
  my $boldface;
  my $msg;
  my $routine;

  my $LANG      = $g_locale_settings{"LANG"};
  my $the_title = set_title ($function_info_ref, $input_filename,
                             "process source");
  my $outfile   = $input_filename . ".html";

#------------------------------------------------------------------------------
# Remove the .txt from file.<n>.src.txt
#------------------------------------------------------------------------------
  my $html_output_file  = $input_filename;
  $html_output_file     =~ s/$txt_ext_regex/.html/;

  gp_message ("debug", $subr_name, "input_filename = $input_filename");
  gp_message ("debug", $subr_name, "the_title = $the_title");

  $file_title  = $the_title;
  $html_header = ${ create_html_header (\$file_title) };
  $html_home   = ${ generate_home_link ("right") };

  push (@modified_html, $html_header);
  push (@modified_html, $html_home);
  push (@modified_html, "<pre>");

#------------------------------------------------------------------------------
# Open the html file used for the output.
#------------------------------------------------------------------------------
  open (NEW_HTML, ">", $html_output_file)
    or die ("$subr_name - unable to open file $html_output_file for writing: '$!'");
  gp_message ("debug", $subr_name , "opened file $html_output_file for writing");

  $base = get_basename ($input_filename);

  gp_message ("debug", $subr_name, "base = $base");

  if ($base =~ /$src_filename_id_regex/)
    {
      my $file_id = $1;
      if (defined ($function_info[$file_id]{"routine"}))
        {
          $routine = $function_info[$file_id]{"routine"};

          gp_message ("debugXL", $subr_name, "target routine = $routine");
        }
      else
        {
          my $msg = "cannot retrieve routine name for file_id = $file_id";
          gp_message ("assertion", $subr_name, $msg);
        }
    }

#------------------------------------------------------------------------------
# Check if the input file is empty.  If so, generate a short text in the html
# file and return.  Otherwise open the file and read the contents.
#------------------------------------------------------------------------------
  $is_empty = is_file_empty ($input_filename);

  if ($is_empty)
    {
#------------------------------------------------------------------------------
# The input file is empty. Write a diagnostic message in the html file and exit.
#------------------------------------------------------------------------------
      gp_message ("debug", $subr_name ,"file $input_filename is empty");

      my $comment = "No source listing generated by $tool_name - " .
                    "file $input_filename is empty";
      my $error_file = $outputdir . "gp-listings.err";

      my $html_empty_file_ref = html_text_empty_file (\$comment, \$error_file);
      my @html_empty_file     = @{ $html_empty_file_ref };

      print NEW_HTML "$_\n" for @html_empty_file;

      close NEW_HTML;

      return (0);
    }
  else
#------------------------------------------------------------------------------
# Open the input file with the source code
#------------------------------------------------------------------------------
    {
      open (SRC_LISTING, "<", $input_filename)
        or die ("$subr_name - unable to open file $input_filename for reading: '$!'");
      gp_message ("debug", $subr_name, "opened file $input_filename for reading");
    }

#------------------------------------------------------------------------------
# Generate the regex for the metrics.  This depends on the number of metrics.
#------------------------------------------------------------------------------
  gp_message ("debug", $subr_name, "decimal_separator = $decimal_separator<--");

  $metric_regex = '';
  $metric_extra_regex = '';
  for my $metric_used (1 .. $number_of_metrics)
    {
      $metric_regex .= '(\d+' . $decimal_separator . '*\d*)\s+';
    }
  $metric_extra_regex = $metric_regex . '(\d+' . $decimal_separator . ')';

  $hot_lines_regex = '^(#{2})\s+';
  $hot_lines_regex .= '('.$metric_regex.')';
  $hot_lines_regex .= '([0-9?]+)\.\s+(.*)';

  $src_times_regex = '^(#{2}|\s{2})\s+';
  $src_times_regex .= '('.$metric_extra_regex.')';
  $src_times_regex .= '(.*)';

  gp_message ("debugXL", $subr_name, "metric_regex   = $metric_regex");
  gp_message ("debugXL", $subr_name, "hot_lines_regex = $hot_lines_regex");
  gp_message ("debugXL", $subr_name, "src_times_regex = $src_times_regex");
  gp_message ("debugXL", $subr_name, "src_regex      = $src_regex");

  gp_message ("debugXL", $subr_name, "end_src1_header_regex = $end_src1_header_regex");
  gp_message ("debugXL", $subr_name, "end_src2_header_regex = $end_src2_header_regex");
  gp_message ("debugXL", $subr_name, "function_regex = $function_regex");
  gp_message ("debugXL", $subr_name, "function2_regex = $function2_regex");
  gp_message ("debugXL", $subr_name, "src_regex = $src_regex");

#------------------------------------------------------------------------------
# Read the file into memory.
#------------------------------------------------------------------------------
  chomp (@file_contents = <SRC_LISTING>);

#------------------------------------------------------------------------------
# Identify the header lines.  Make the minimal assumptions.
#
# In both cases, the first line after the header has whitespace.  This is
# followed by either one of the following:
#
# - <line_no>.
# - <Function:
#
# These are the characteristics we use below.
#------------------------------------------------------------------------------
  for (my $line_number=0; $line_number <= $#file_contents; $line_number++)
    {
      $input_line = $file_contents[$line_number];

#------------------------------------------------------------------------------
# We found the first source code line.  Bail out.
#------------------------------------------------------------------------------
      if (($input_line =~ /$end_src1_header_regex/) or
          ($input_line =~ /$end_src2_header_regex/))
        {
          gp_message ("debugXL", $subr_name, "header time is over - hit source line");
          gp_message ("debugXL", $subr_name, "line_number = $line_number");
          gp_message ("debugXL", $subr_name, "input_line = $input_line");
          last;
        }
      else
#------------------------------------------------------------------------------
# Store the header lines in the html structure.
#------------------------------------------------------------------------------
        {
          $modified_line = "<i>" . $input_line . "</i>";
          push (@modified_html, $modified_line);
        }
    }
#------------------------------------------------------------------------------
# We know the source code starts at this index value:
#------------------------------------------------------------------------------
  $start_all_source = scalar (@modified_html);
  gp_message ("debugXL", $subr_name, "source starts at start_all_source = $start_all_source");

#------------------------------------------------------------------------------
# Scan the file to identify where the target source starts and ends.
#------------------------------------------------------------------------------
  gp_message ("debugXL", $subr_name, "search for target function $routine");
  $start_tracking = $FALSE;
  $found_target   = $FALSE;
  for (my $line_number=0; $line_number <= $#file_contents; $line_number++)
    {
      $input_line = $file_contents[$line_number];

      gp_message ("debugXL", $subr_name, "[$line_number] $input_line");

      if ($input_line =~ /$function_regex/)
        {
          if (defined ($1) and defined ($2))
            {
              $func_name_in_src_file = $2;
              my $msg = "found a function - name = $func_name_in_src_file";
              gp_message ("debugXL", $subr_name, $msg);

              if ($start_tracking)
                {
                  $start_tracking = $FALSE;
                  $end_target_source = $line_number - 1;
                  my $msg =  "end_target_source = $end_target_source";
                  gp_message ("debugXL", $subr_name, $msg);
                  last;
                }

              if ($func_name_in_src_file eq $routine)
                {
                  $found_target        = $TRUE;
                  $start_tracking      = $TRUE;
                  $start_target_source = $line_number;

                  gp_message ("debugXL", $subr_name, "found target function $routine");
                  gp_message ("debugXL", $subr_name, "function_name = $2 routine = $routine");
                  gp_message ("debugXL", $subr_name, "routine = $routine start_tracking = $start_tracking");
                  gp_message ("debugXL", $subr_name, "start_target_source = $start_target_source");
                }
            }
          else
            {
              my $msg = "parsing line $input_line";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
    }

#------------------------------------------------------------------------------
# This is not supposed to happen, but it is not a fatal error either.  The
# hyperlinks related to this function will not work, so a warning is issued.
# A message is issued both in debug mode, and as a warning.
#------------------------------------------------------------------------------
  if (not $found_target)
    {
      my $msg;

      $msg = "target function $routine not found in $base - " .
             "links to source code involving this function will not work";
      gp_message ("debug", $subr_name, $msg);
      gp_message ("warning", $subr_name, $msg);
      $g_total_warning_count++;

      return ($found_target);
    }

#------------------------------------------------------------------------------
# Catch the line number of the last function.
#------------------------------------------------------------------------------
  if ($start_tracking)
    {
      $end_target_source = $#file_contents;
    }
  gp_message ("debugXL", $subr_name, "routine = $routine start_tracking = $start_tracking");
  gp_message ("debugXL", $subr_name, "start_target_source = $start_target_source");
  gp_message ("debugXL", $subr_name, "end_target_source   = $end_target_source");

#------------------------------------------------------------------------------
# We now have the index range for the function of interest and will parse it.
# Since we already handled the first line with the function marker, we start
# with the line following.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# Find the hot source lines and store them.
#------------------------------------------------------------------------------
  gp_message ("debugXL", $subr_name, "determine the maximum metric values");
  for (my $line_number=$start_target_source+1; $line_number <= $end_target_source; $line_number++)
    {
      $input_line = $file_contents[$line_number];
      gp_message ("debugXL", $subr_name, " $line_number : check input_line = $input_line");

      if ( $input_line =~ /$hot_lines_regex/ )
        {
          gp_message ("debugXL", $subr_name, " $line_number : found a hot line");
#------------------------------------------------------------------------------
# We found a hot line and the metric fields are stored in $2.  We turn this
# string into an array and add it as a row to hot_source_lines.
#------------------------------------------------------------------------------
              $hot_line      = $1;
              $metric_values = $2;

              gp_message ("debugXL", $subr_name, "hot_line = $hot_line");
              gp_message ("debugXL", $subr_name, "metric_values = $metric_values");

              my @metrics = split (" ", $metric_values);
              push (@hot_source_lines, [@metrics]);
        }
      gp_message ("debugXL", $subr_name, " $line_number : completed check for hot line");
    }

#------------------------------------------------------------------------------
# Transpose the array with the hot lines.  This means each row has all the
# values for a metrict and it makes it easier to determine the maximum values.
#------------------------------------------------------------------------------
  for my $row (keys @hot_source_lines)
    {
      my $msg = "row[" . $row . "] =";
      for my $col (keys @{$hot_source_lines[$row]})
        {
          $msg .= " $hot_source_lines[$row][$col]";
          $transposed_hot_lines[$col][$row] = $hot_source_lines[$row][$col];
        }
    }

#------------------------------------------------------------------------------
# Print the maximum metric values found.  Each row contains the data for a
# different metric.
#------------------------------------------------------------------------------
  for my $row (keys @transposed_hot_lines)
    {
      my $msg = "row[" . $row . "] =";
      for my $col (keys @{$transposed_hot_lines[$row]})
        {
          $msg .= " $transposed_hot_lines[$row][$col]";
        }
      gp_message ("debugXL", $subr_name, "hot lines = $msg");
    }

#------------------------------------------------------------------------------
# Determine the maximum value for each metric.
#------------------------------------------------------------------------------
  for my $row (keys @transposed_hot_lines)
    {
      my $max_val = 0;
      for my $col (keys @{$transposed_hot_lines[$row]})
        {
          $max_val = max ($transposed_hot_lines[$row][$col], $max_val);
        }
#------------------------------------------------------------------------------
# Convert to a floating point number.
#------------------------------------------------------------------------------
      if ($max_val =~ /$integer_only_regex/)
        {
          $max_val = sprintf ("%f", $max_val);
        }
      push (@max_metric_values, $max_val);
    }

    for my $metric (keys @max_metric_values)
      {
        my $msg = "$input_filename max_metric_values[$metric] = " .
                  $max_metric_values[$metric];
        gp_message ("debugXL", $subr_name, $msg);
      }

#------------------------------------------------------------------------------
# Process those functions that are not the current target.
#------------------------------------------------------------------------------
  $modified_html_ref = process_non_target_source ($start_all_source,
                                                  $start_target_source-1,
                                                  $src_times_regex,
                                                  $function_regex,
                                                  $number_of_metrics,
                                                  \@file_contents,
                                                  \@modified_html);
  @modified_html = @{ $modified_html_ref };

#------------------------------------------------------------------------------
# This is the core part to process the information for the target function.
#------------------------------------------------------------------------------
  gp_message ("debugXL", $subr_name, "parse and process the target source");
  $modified_html_ref = process_target_source ($start_target_source,
                                              $end_target_source,
                                              $routine,
                                              \@max_metric_values,
                                              $src_times_regex,
                                              $function2_regex,
                                              $number_of_metrics,
                                              \@file_contents,
                                              \@modified_html);
  @modified_html = @{ $modified_html_ref };

  if ($end_target_source < $#file_contents)
    {
      $modified_html_ref = process_non_target_source ($end_target_source+1,
                                                      $#file_contents,
                                                      $src_times_regex,
                                                      $function_regex,
                                                      $number_of_metrics,
                                                      \@file_contents,
                                                      \@modified_html);
      @modified_html = @{ $modified_html_ref };
    }

  gp_message ("debug", $subr_name, "completed reading source");

#------------------------------------------------------------------------------
# Add an extra line with diagnostics.
#
# TBD: The same is done in generate_dis_html but should be done only once.
#------------------------------------------------------------------------------
  if ($hp_value > 0)
    {
      my $rounded_percentage = sprintf ("%.1f", $hp_value);
      $threshold_line = "<i>The setting for the highlight percentage";
      $threshold_line .= " (--highlight-percentage) option:";
      $threshold_line .= " " . $rounded_percentage . " (%)</i>";
    }
  else
    {
      $threshold_line  = "<i>The highlight percentage feature has not been";
      $threshold_line .= " enabled</i>";
    }

  $html_home = ${ generate_home_link ("left") };
  $html_end  = ${ terminate_html_document () };

  push (@modified_html, "</pre>");
  push (@modified_html, "<br>");
  push (@modified_html, $threshold_line);
  push (@modified_html, $html_home);
  push (@modified_html, "<br>");
  push (@modified_html, $g_html_credits_line);
  push (@modified_html, $html_end);

  for my $i (0 .. $#modified_html)
    {
      gp_message ("debugXL", $subr_name, "[$i] -> $modified_html[$i]");
    }

#------------------------------------------------------------------------------
# Write the generated HTML text to file.
#------------------------------------------------------------------------------
  for my $i (0 .. $#modified_html)
    {
      print NEW_HTML "$modified_html[$i]" . "\n";
    }
  close (NEW_HTML);
  close (SRC_LISTING);

  return ($found_target);

} #-- End of subroutine process_source

#------------------------------------------------------------------------------
# Process the source lines for the target function.
#------------------------------------------------------------------------------
sub process_target_source
{
  my $subr_name = get_my_name ();

  my ($start_scan, $end_scan, $target_function, $max_metric_values_ref,
      $src_times_regex, $function2_regex, $number_of_metrics,
      $file_contents_ref, $modified_html_ref) = @_;

  my @file_contents = @{ $file_contents_ref };
  my @modified_html = @{ $modified_html_ref };
  my @max_metric_values = @{ $max_metric_values_ref };

  my @components = ();

  my $colour_coded_line;
  my $colour_coded_line_ref;
  my $hot_line;
  my $input_line;
  my $line_id;
  my $modified_line;
  my $metric_values;
  my $src_code_line;
  my $src_line_no;

  gp_message ("debug", $subr_name, "parse and process the core loop");

  for (my $line_number=$start_scan; $line_number <= $end_scan; $line_number++)
    {
      $input_line = $file_contents[$line_number];

#------------------------------------------------------------------------------
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
      $input_line =~ s/$g_less_than_regex/$g_html_less_than_regex/g;

      $line_id = extract_source_line_number ($src_times_regex,
                                             $function2_regex,
                                             $number_of_metrics,
                                             $input_line);

      gp_message ("debug", $subr_name, "line_number = $line_number : input_line = $input_line line_id = $line_id");

      if ($input_line =~ /$function2_regex/)
#------------------------------------------------------------------------------
# Found the function marker.
#------------------------------------------------------------------------------
        {
          if (defined ($1) and defined ($2))
            {
              my $func_name_in_file = $2;
              my $spaces = $1;
              my $boldface = $TRUE;
              gp_message ("debug", $subr_name, "function_name = $2");
              my $function_line       = "&lt;Function: " . $func_name_in_file . ">";
              my $color_function_name = color_string (
                                          $function_line,
                                          $boldface,
                                          $g_html_color_scheme{"target_function_name"});
              my $ftag;
              if (exists ($g_function_tag_id{$target_function}))
                {
                  $ftag = $g_function_tag_id{$target_function};
                  gp_message ("debug", $subr_name, "target_function = $target_function ftag = $ftag");
                }
              else
                {
                  my $msg = "no ftag found for $target_function";
                  gp_message ("assertion", $subr_name, $msg);
                }
              $modified_line = "<a id=\"" . $ftag . "\"></a>";
              $modified_line .= $spaces . "<i>" . $color_function_name . "</i>";
            }
        }
      elsif ($input_line =~ /$src_times_regex/)
#------------------------------------------------------------------------------
# This is a line with metric values.
#------------------------------------------------------------------------------
        {
          gp_message ("debug", $subr_name, "input line has metrics");

          $hot_line      = $1;
          $metric_values = $2;
          $src_line_no   = $3;
          $src_code_line = $4;

          gp_message ("debug", $subr_name, "hot_line = $hot_line");
          gp_message ("debug", $subr_name, "metric_values = $metric_values");
          gp_message ("debug", $subr_name, "src_line_no = $src_line_no");
          gp_message ("debug", $subr_name, "src_code_line = $src_code_line");

          if ($hot_line eq "##")
#------------------------------------------------------------------------------
# Highlight the most expensive line.
#------------------------------------------------------------------------------
            {
              @components = split (" ", $input_line, 1+$number_of_metrics+2);
              $modified_line = set_background_color_string (
                                 $input_line,
                                 $g_html_color_scheme{"background_color_hot"});
            }
          else
            {
#------------------------------------------------------------------------------
# Highlight those lines close enough to the most expensive line.
#------------------------------------------------------------------------------
              @components = split (" ", $input_line, $number_of_metrics + 2);
              for my $i (0 .. $number_of_metrics-1)
                {
                  gp_message ("debugXL", $subr_name, "$line_number : time check components[$i] = $components[$i]");
                }

              $colour_coded_line_ref = check_metric_values ($metric_values, \@max_metric_values);

              $colour_coded_line = $ {$colour_coded_line_ref};
              if ($colour_coded_line)
                {
                  gp_message ("debugXL", $subr_name, "$line_number : change background colour modified_line = $modified_line");
                  $modified_line = set_background_color_string ($input_line, $g_html_color_scheme{"background_color_lukewarm"});
                }
              else
                {
                  $modified_line = "<a id=\"line_" . $line_id . "\"></a>";
                  $modified_line .= "$input_line";
                }
            }
        }
      else
#------------------------------------------------------------------------------
# This is a regular line that is not modified.
#------------------------------------------------------------------------------
        {
#------------------------------------------------------------------------------
# Add an id.
#------------------------------------------------------------------------------
          gp_message ("debug", $subr_name, "$line_number : input line is a regular line");
          $modified_line = "<a id=\"line_" . $line_id . "\"></a>";
          $modified_line .= "$input_line";
        }
      gp_message ("debug", $subr_name, "$line_number : mod = $modified_line");
      push (@modified_html, $modified_line);
    }

  return (\@modified_html);

} #-- End of subroutine process_target_source

#------------------------------------------------------------------------------
# Process the options.  Set associated variables and check the options for
# correctness.  For example, detect if conflicting options have been set.
#------------------------------------------------------------------------------
sub process_user_options
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref) = @_;

  my @exp_dir_list = @{ $exp_dir_list_ref };

  my %ignored_metrics = ();

  my $abs_path_dir;
  my @candidate_ignored_metrics = ();
  my $error_code;
  my $hp_value;
  my $msg;

  my $outputdir;

  my $target_cmd;
  my $rm_output_msg;
  my $mkdir_output_msg;
  my $time_percentage_multiplier;
  my $process_all_functions;

#------------------------------------------------------------------------------
# The -o and -O options are mutually exclusive.
#------------------------------------------------------------------------------
  my $define_new_output_dir = $g_user_settings{"output"}{"defined"};
  my $overwrite_output_dir  = $g_user_settings{"overwrite"}{"defined"};
  my $dir_o_option          = $g_user_settings{"output"}{"current_value"};
  my $dir_O_option          = $g_user_settings{"overwrite"}{"current_value"};

  if ($define_new_output_dir and $overwrite_output_dir)
    {
      $msg  = "the -o/--output and -O/--overwrite options are both set, " .
              "but are mutually exclusive";
      gp_message ("error", $subr_name, $msg);

      $msg  = "(setting for -o = $dir_o_option, " .
              "setting for -O = $dir_O_option)";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }

#------------------------------------------------------------------------------
# The warnings option is deprecated.  Print a warning to this extent and point
# to the --nowarnings option.
#------------------------------------------------------------------------------
#------------------------------------------------------------------------------
# Handle the situation that both or one of the highlight-percentage and hp
# options are set.
#------------------------------------------------------------------------------
  if ($g_user_settings{"warnings"}{"defined"})
    {
      $msg  = "<br>" . "the --warnings option has been deprecated and";
      $msg .= " will be ignored";
      gp_message ("warning", $subr_name, $msg);

      if ($g_user_settings{"nowarnings"}{"defined"})
        {
          $msg  = "since the --nowarnings option is also used, warnings";
          $msg .= " are disabled";
          gp_message ("warning", $subr_name, $msg);
        }
      else
        {
          $msg = "by default, warnings are enabled and can be disabled with";
          gp_message ("warning", $subr_name, $msg);
          $msg = " the --nowarnings option";
          gp_message ("warning", $subr_name, $msg);
        }
      $g_total_warning_count++;
    }

#------------------------------------------------------------------------------
# In case both the --highlight-percentage and -hp option are set, issue a
# warning and continue with the --highlight-percentage value.
#------------------------------------------------------------------------------
  if ($g_user_settings{"hp"}{"defined"})
    {
      $msg  = "<br>" . "the -hp option has been deprecated and";
      $msg .= " will be ignored";
      gp_message ("warning", $subr_name, $msg);

      if ($g_user_settings{"highlight_percentage"}{"defined"})
        {
          $msg  = "since the --highlight-percentage option is also used,";
          $msg .= " the value of ";
          $msg .= $g_user_settings{"highlight_percentage"}{"current_value"};
          $msg .= " will be applied";
          gp_message ("warning", $subr_name, $msg);
        }
      else
        {
#------------------------------------------------------------------------------
# If only the -hp option is set, we use it, because we do not want to break
# compatibility (yet) and force the user to change the option.
#------------------------------------------------------------------------------

## FUTURE          $msg  = "instead, the default setting of "
## FUTURE          $msg .= $g_user_settings{"highlight_percentage"}{"current_value"};
## FUTURE          $msg .= " for the --highlight-percentage will be used";
## FUTURE          gp_message ("warning", $subr_name, $msg);

## FUTURE          $msg = "please use this option to set the highlighting value";
## FUTURE          gp_message ("warning", $subr_name, $msg);

          $g_user_settings{"highlight_percentage"}{"current_value"} =
          $g_user_settings{"hp"}{"current_value"};

          $g_user_settings{"highlight_percentage"}{"defined"} = $TRUE;

          $msg = "for now, the value of " .
                 $g_user_settings{"hp"}{"current_value"} .
                 " for the -hp option is used, but please change the" .
                 " option to --highlight-percentage";
          gp_message ("warning", $subr_name, $msg);
        }

      $g_total_warning_count++;
    }

#------------------------------------------------------------------------------
# Regardless of the use of the -hp option, we continue with the value for
# highlight-percentage.  Some more checks are carried out now.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# This value should be in the interval [0,100].
# the number to be positive, but the limits have not been checked yet.
#------------------------------------------------------------------------------
  $hp_value = $g_user_settings{"highlight_percentage"}{"current_value"};

  if (($hp_value < 0) or ($hp_value > 100))
    {
      $msg  = "the value for the highlight percentage is set to $hp_value,";
      $msg .= " but must be in the range [0, 100]";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }
  elsif ($hp_value == 0.0)
#------------------------------------------------------------------------------
# A value of zero is interpreted to mean that highlighting should be disabled.
# To make the checks for this later on easier, set it to an integer value of 0.
#------------------------------------------------------------------------------
    {
      $g_user_settings{"highlight_percentage"}{"current_value"} = 0;

      $msg  = "reset the highlight percentage value from 0.0 to";
      $msg .= " " . $g_user_settings{"highlight_percentage"}{"current_value"};
      gp_message ("debug", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# The value for TP should be in the interval (0,100].  We already enforced
# the number to be positive, but the limits have not been checked yet.
#------------------------------------------------------------------------------
  my $tp_value = $g_user_settings{"threshold_percentage"}{"current_value"};

  if (($tp_value < 0) or ($tp_value > 100))
    {
      $msg  = "the value for the total percentage is set to $tp_value,";
      $msg .=   " but must be in the range (0, 100]";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }
  else
    {
      $time_percentage_multiplier = $tp_value/100.0;

# Ruud  if (($TIME_PERCENTAGE_MULTIPLIER*100.) >= 100.)

      if ($tp_value == 100)
        {
          $process_all_functions = $TRUE; # ensure that all routines are handled
        }
      else
        {
          $process_all_functions = $FALSE;
        }

      $msg = "value of time_percentage_multiplier = " .
             $time_percentage_multiplier;
      gp_message ("debugM", $subr_name, $msg);
      $msg = "value of process_all_functions      = " .
             ($process_all_functions ? "TRUE" : "FALSE");
      gp_message ("debugM", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# If imetrics has been set, split the list into the individual metrics that
# need to be excluded.  The associated hash called $ignore_metrics has the
# to be excluded metrics as an index.  The value of $TRUE assigned does not
# really matter.
#------------------------------------------------------------------------------
  if ($g_user_settings{"ignore_metrics"}{"defined"})
    {
      @candidate_ignored_metrics =
              split (":", $g_user_settings{"ignore_metrics"}{"current_value"});
    }
  for my $metric (@candidate_ignored_metrics)
    {
# TBD: bug?      $ignored_metrics{$metric} = $FALSE;
      $ignored_metrics{$metric} = $TRUE;
    }
  for my $metric (keys %ignored_metrics)
    {
      my $msg = "ignored_metrics{$metric} = $ignored_metrics{$metric}";
      gp_message ("debugM", $subr_name, $msg);
    }

#------------------------------------------------------------------------------
# Check if the experiment directories exist and if they do, add the absolute
# path.  This is easier in the remainder.
#------------------------------------------------------------------------------
  for my $i (0 .. $#exp_dir_list)
    {
      if (-d $exp_dir_list[$i])
        {
          $abs_path_dir = Cwd::abs_path ($exp_dir_list[$i]);
          $exp_dir_list[$i] = $abs_path_dir;

          $msg = "directory $exp_dir_list[$i] exists";
          gp_message ("debugM", $subr_name, $msg);
        }
    }

  return (\%ignored_metrics, $outputdir, $time_percentage_multiplier,
	  $process_all_functions, \@exp_dir_list);

} #-- End of subroutine process_user_options

#------------------------------------------------------------------------------
# This function addresses a legacy issue.
#
# In binutils 2.40, the "gprofng display text" tool may add a string in the
# function overviews.  This did not add any value and was disruptive to the
# output.  It has been removed in 2.41, but in order to support the older
# versions of gprofng, the string is removed before the data is processed.
#
# Note: the double space in "--  no" is not a typo in this code!
#------------------------------------------------------------------------------
sub remove_redundant_string
{
  my $subr_name = get_my_name ();

  my ($target_array_ref) = @_;

  my @target_array = @{ $target_array_ref };

  my $msg;
  my $redundant_string = " --  no functions found";

  for (my $line = 0; $line <= $#target_array; $line++)
    {
      $target_array[$line] =~ s/$redundant_string//;
    }

  $msg = "removed any occurrence of " . $redundant_string;
  gp_message ("debugM", $subr_name, $msg);

  return (\@target_array);

} #-- End of subroutine remove_redundant_string

#------------------------------------------------------------------------------
# This is a hopefully temporary routine to disable/ignore selected user
# settings.  As the functionality expands, this list will get shorter.
#------------------------------------------------------------------------------
sub reset_selected_settings
{
  my $subr_name = get_my_name ();

  $g_locale_settings{"decimal_separator"} = "\\.";
  $g_locale_settings{"convert_to_dot"}    = $FALSE;
  $g_user_settings{func_limit}{current_value} = 1000000;

  gp_message ("debug", $subr_name, "reset selected settings");

  return (0);

} #-- End of subroutine reset_selected_settings

#------------------------------------------------------------------------------
# There may be various different visibility characters in a metric definition.
# For example: e+%CPI.
#
# Internally we use a normalized definition that only uses the dot (e.g.
# e.CPI) as an index into the description structure.
#
# Here we reduce the incoming metric definition to the normalized form, look
# up the text, and return a pointer to it.
#------------------------------------------------------------------------------
sub retrieve_metric_description
{
  my $subr_name = get_my_name ();

  my ($metric_name_ref, $metric_description_ref) = @_;

  my $metric_name        = ${ $metric_name_ref };
  my %metric_description = %{ $metric_description_ref };

  my $description;
  my $normalized_metric;

  $metric_name =~ /([ei])([\.\+%]+)(.*)/;

  if (defined ($1) and defined ($3))
    {
      $normalized_metric = $1 . "." . $3;
    }
  else
    {
      my $msg = "metric $metric_name has an unknown format";
      gp_message ("assertion", $subr_name, $msg);
    }

  if (defined ($metric_description{$normalized_metric}))
    {
      $description = $metric_description{$normalized_metric};
    }
  else
    {
      my $msg = "description for normalized metric $normalized_metric not found";
      gp_message ("assertion", $subr_name, $msg);
    }

  return (\$description);

} #-- End of subroutine retrieve_metric_description

#------------------------------------------------------------------------------
# TBD.
#------------------------------------------------------------------------------
sub rnumerically
{
  my ($f1,$f2);
  if ($a =~ /^([^\d]*)(\d+)/)
    {
      $f1 = int ($2);
      if ($b=~ /^([^\d]*)(\d+)/)
        {
          $f2 = int ($2);
          $f1 == $f2 ? 0 : ($f1 > $f2 ? -1 : +1);
        }
    }
  else
    {
      return ($b <=> $a);
    }
} #-- End of subroutine rnumerically

#------------------------------------------------------------------------------
# TBD: Remove - not used any longer.
# Set the architecture and associated regular expressions.
#------------------------------------------------------------------------------
sub set_arch_and_regexes
{
  my $subr_name = get_my_name ();

  my ($arch_uname) = @_;

  my $architecture_supported;

  gp_message ("debug", $subr_name, "arch_uname = $arch_uname");

  if ($arch_uname eq "x86_64")
    {
      #x86/x64 hardware uses jump
      $architecture_supported = $TRUE;
#      $arch='x64';
#      $regex=':\s+(j).*0x[0-9a-f]+';
#      $subexp='(\[\s*)(0x[0-9a-f]+)';
#      $linksubexp='(\[\s*)(0x[0-9a-f]+)';
      gp_message ("debug", $subr_name, "detected $arch_uname hardware");

      $architecture_supported = $TRUE;
      $g_arch_specific_settings{"arch_supported"}  = $TRUE;
      $g_arch_specific_settings{"arch"}       = 'x64';
      $g_arch_specific_settings{"regex"}     = ':\s+(j).*0x[0-9a-f]+';
      $g_arch_specific_settings{"subexp"}     = '(\[\s*)(0x[0-9a-f]+)';
      $g_arch_specific_settings{"linksubexp"} = '(\[\s*)(0x[0-9a-f]+)';
    }
#------------------------------------------------------------------------------
# TBD: Remove the elsif block
#------------------------------------------------------------------------------
  elsif ($arch_uname=~m/sparc/s)
    {
      #sparc hardware uses branch
      $architecture_supported = $FALSE;
#      $arch='sparc';
#      $regex=':\s+(c|b|fb).*0x[0-9a-f]+\s*$';
#      $subexp='(\s*)(0x[0-9a-f]+)\s*$';
#      $linksubexp='(\s*)(0x[0-9a-f]+\s*$)';
#      gp_message ("debug", $subr_name, "detected $arch_uname hardware arch = $arch - this is no longer supported");
      $architecture_supported = $FALSE;
      $g_arch_specific_settings{arch_supported}  = $FALSE;
      $g_arch_specific_settings{arch}       = 'sparc';
      $g_arch_specific_settings{regex}     = ':\s+(c|b|fb).*0x[0-9a-f]+\s*$';
      $g_arch_specific_settings{subexp}     = '(\s*)(0x[0-9a-f]+)\s*$';
      $g_arch_specific_settings{linksubexp} = '(\s*)(0x[0-9a-f]+\s*$)';
    }
  else
    {
      $architecture_supported = $FALSE;
      gp_message ("debug", $subr_name, "detected $arch_uname hardware - this not supported; limited functionality");
    }

    return ($architecture_supported);

} #-- End of subroutine set_arch_and_regexes

#------------------------------------------------------------------------------
# Set the background color of the input string.
#
# For supported colors, see:
# https://www.w3schools.com/colors/colors_names.asp
#------------------------------------------------------------------------------
sub set_background_color_string
{
  my $subr_name = get_my_name ();

  my ($input_string, $color) = @_;

  my $background_color_string;
  my $msg;

  $msg = "color = $color input_string = $input_string";
  gp_message ("debugXL", $subr_name, $msg);

  $background_color_string = "<span style='background-color: " . $color .
                             "'>" . $input_string . "</span>";

  $msg = "color = $color background_color_string = " .
         $background_color_string;
  gp_message ("debugXL", $subr_name, $msg);

  return ($background_color_string);

} #-- End of subroutine set_background_color_string

#------------------------------------------------------------------------------
# Set the g_debug_size structure for a given value for "size".  Also set the
# value in $g_user_settings{"debug"}{"current_value"}
#------------------------------------------------------------------------------
sub set_debug_size
{
  my $subr_name = get_my_name ();

  my $debug_value = lc ($g_user_settings{"debug"}{"current_value"});

#------------------------------------------------------------------------------
# Set the corresponding sizes in the table.  A value of "on" is equivalent to
# size "s".
#------------------------------------------------------------------------------
  if (($debug_value eq "on") or ($debug_value eq "s"))
    {
      $g_debug_size{"on"} = $TRUE;
      $g_debug_size{"s"}  = $TRUE;
    }
  elsif ($debug_value eq "m")
    {
      $g_debug_size{"on"} = $TRUE;
      $g_debug_size{"s"}  = $TRUE;
      $g_debug_size{"m"}  = $TRUE;
    }
  elsif ($debug_value eq "l")
    {
      $g_debug_size{"on"} = $TRUE;
      $g_debug_size{"s"}  = $TRUE;
      $g_debug_size{"m"}  = $TRUE;
      $g_debug_size{"l"}  = $TRUE;
    }
  elsif ($debug_value eq "xl")
    {
      $g_debug_size{"on"} = $TRUE;
      $g_debug_size{"s"}  = $TRUE;
      $g_debug_size{"m"}  = $TRUE;
      $g_debug_size{"l"}  = $TRUE;
      $g_debug_size{"xl"} = $TRUE;
    }
  else
#------------------------------------------------------------------------------
# Any other value is considered to disable debugging.
#------------------------------------------------------------------------------
    {
##      $g_user_settings{"debug"}{"current_value"} = "off";
      $g_debug            = $FALSE;
      $g_debug_size{"on"} = $FALSE;
      $g_debug_size{"s"}  = $FALSE;
      $g_debug_size{"m"}  = $FALSE;
      $g_debug_size{"l"}  = $FALSE;
      $g_debug_size{"xl"} = $FALSE;
    }

#------------------------------------------------------------------------------
# Activate in case of an emergency :-)
#------------------------------------------------------------------------------
  my $show_sizes = $FALSE;

  if ($show_sizes)
    {
      if ($g_debug_size{$debug_value})
        {
          for my $i (keys %g_debug_size)
            {
              print "$subr_name g_debug_size{$i} = $g_debug_size{$i}\n";
            }
        }
    }

  return (0);

} #-- End of subroutine set_debug_size

#------------------------------------------------------------------------------
# This subroutine defines the default metrics.
#------------------------------------------------------------------------------
sub set_default_metrics
{
  my $subr_name = get_my_name ();

  my ($outfile1, $ignored_metrics_ref) = @_;

  my %ignored_metrics = %{ $ignored_metrics_ref };

  my %metric_description = ();
  my %metric_found       = ();

  my $detail_metrics;
  my $detail_metrics_system;

  my $call_metrics    = "";
  my $summary_metrics = "";

  open (METRICS, "<", $outfile1)
    or die ("Unable to open metrics file $outfile1 for reading - '$!'");
  gp_message ("debug", $subr_name, "opened $outfile1 for reading");

  while (<METRICS>)
    {
      my $metric_line = $_;
      chomp ($metric_line);

      gp_message ("debug", $subr_name,"the value of metric_line = $metric_line");

#------------------------------------------------------------------------------
# Decode the metric part of the input line. If a valid line, return the
# metric components. Otherwise return "skipped" in the metric_spec field.
#------------------------------------------------------------------------------
      my ($metric_spec, $metric_flavor, $metric_visibility, $metric_name,
                $metric_description) = extract_metric_specifics ($metric_line);

      gp_message ("debug", $subr_name, "metric_spec   = $metric_spec");
      gp_message ("debug", $subr_name, "metric_flavor = $metric_flavor");

      if ($metric_spec eq "skipped")
#------------------------------------------------------------------------------
# Not a valid input line.
#------------------------------------------------------------------------------
        {
          gp_message ("debug", $subr_name, "skipped line: $metric_line");
        }
      else
        {
#------------------------------------------------------------------------------
# A valid metric field has been found.
#------------------------------------------------------------------------------
          gp_message ("debug", $subr_name, "metric_name        = $metric_name");
          gp_message ("debug", $subr_name, "metric_description = $metric_description");

#        if (exists ($IMETRICS{$m})){
          if ($g_user_settings{"ignore_metrics"}{"defined"} and exists ($ignored_metrics{$metric_name}))
            {
              gp_message ("debug", $subr_name, "user requested to ignore metric $metric_name");
              next;
            }

#------------------------------------------------------------------------------
# Only the exclusive metric is selected.
#------------------------------------------------------------------------------
          if ($metric_flavor eq "e")
            {
              $metric_found{$metric_spec}       = $TRUE;
              $metric_description{$metric_spec} = $metric_description;

# TBD: remove the -AO:
              gp_message ("debug", $subr_name,"-AO metric_description{$metric_spec} = $metric_description{$metric_spec}");

              $summary_metrics .= $metric_spec.":";
              $call_metrics .= "a.".$metric_name.":";
            }
        }
    }
  close (METRICS);

  chop ($call_metrics);
  chop ($summary_metrics);

  $detail_metrics        = $summary_metrics;
  $detail_metrics_system = $summary_metrics;

  return (\%metric_description, \%metric_found,
         $summary_metrics, $detail_metrics, $detail_metrics_system, $call_metrics);

} #-- End of subroutine set_default_metrics

#------------------------------------------------------------------------------
# Set various system specific variables.  These depend upon both the processor
# architecture and OS. The values are stored in global structure
# g_arch_specific_settings.
#------------------------------------------------------------------------------
sub set_system_specific_variables
{
  my $subr_name = get_my_name ();

  my ($arch_uname, $arch_uname_s) = @_;

  my $elf_arch;
  my $read_elf_cmd;
  my $elf_support;
  my $architecture_supported;
  my $arch;
  my $regex;
  my $subexp;
  my $linksubexp;

  if ($arch_uname eq "x86_64")
    {
#------------------------------------------------------------------------------
# x86/x64 hardware uses jump
#------------------------------------------------------------------------------
      $architecture_supported = $TRUE;
      $arch       = 'x64';
      $regex     =':\s+(j).*0x[0-9a-f]+';
      $subexp     ='(\[\s*)(0x[0-9a-f]+)';
      $linksubexp ='(\[\s*)(0x[0-9a-f]+)';

#      gp_message ("debug", $subr_name, "detected $arch_uname hardware arch = $arch");

      $g_arch_specific_settings{"arch_supported"} = $TRUE;
      $g_arch_specific_settings{"arch"}           = 'x64';
#------------------------------------------------------------------------------
# Define the regular expressions to parse branch instructions.
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# TBD: Need much more than these
#------------------------------------------------------------------------------
      $g_arch_specific_settings{"regex"} = '\.*([0-9a-fA-F]*):\s+(j).*\s*0x([0-9a-fA-F]+)';
      $g_arch_specific_settings{"subexp"} = '(0x[0-9a-f]+)';
      $g_arch_specific_settings{"linksubexp"} = '(\s*)(0x[0-9a-f]+)';
    }
  else
    {
      $architecture_supported = $FALSE;
      $g_arch_specific_settings{"arch_supported"}  = $FALSE;
    }

#------------------------------------------------------------------------------
# TBD Ruud: need to handle this better
#------------------------------------------------------------------------------
  if ($arch_uname_s eq "Linux")
    {
      $elf_arch     = $arch_uname_s;
      $read_elf_cmd = $g_mapped_cmds{"readelf"};

      if ($read_elf_cmd eq "road to nowhere")
        {
          $elf_support = $FALSE;
        }
      else
        {
          $elf_support = $TRUE;
        }
      gp_message ("debugXL", $subr_name, "elf_support = $elf_support read_elf_cmd = $read_elf_cmd elf_arch = $elf_arch");
    }
  else
    {
      gp_message ("abort", $subr_name, "the $arch_uname_s operating system is not supported");
    }

  return ($architecture_supported, $elf_arch, $elf_support);

} #-- End of subroutine set_system_specific_variables

#------------------------------------------------------------------------------
# TBD
#------------------------------------------------------------------------------
sub set_title
{
  my $subr_name = get_my_name ();

  my ($function_info_ref, $func, $from_where) = @_ ;

  my $msg;
  my @function_info = @{$function_info_ref};
  my $filename = $func ;

  my $base;
  my $first_line;
  my $file_is_empty;
  my $src_file;
  my $RI;
  my $the_title;
  my $routine = "?";
  my $DIS;
  my $SRC;

  chomp ($filename);

  $base = get_basename ($filename);

  gp_message ("debug", $subr_name, "from_where = $from_where");
  gp_message ("debug", $subr_name, "base = $base filename = $filename");

  if ($from_where eq "process source")
    {
      if ($base =~ /^file\.(\d+)\.src\.txt$/)
        {
          if (defined ($1))
            {
              $RI = $1;
            }
          else
            {
              $msg = "unexpected error encountered parsing $filename";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
      $the_title = "Source";
    }
  elsif ($from_where eq "disassembly")
    {
      if ($base =~ /^file\.(\d+)\.dis$/)
        {
          if (defined ($1))
            {
              $RI = $1;
            }
          else
            {
              $msg = "unexpected error encountered parsing $filename";
              gp_message ("assertion", $subr_name, $msg);
            }
        }
      $the_title = "Disassembly";
    }
  else
    {
      $msg = "called from unknown routine - $from_where";
      gp_message ("assertion", $subr_name, $msg);
    }

  if (defined ($function_info[$RI]{"routine"}))
    {
      $routine = $function_info[$RI]{"routine"};
    }

  if ($from_where eq "process source")
    {
      $file_is_empty = is_file_empty ($filename);

      if ($file_is_empty)
        {
          $src_file = "";
        }
      else
        {
          open ($SRC, "<", $filename)
            or die ("$subr_name - unable to open source file $filename for reading:'$!'");
          gp_message ("debug", $subr_name, "opened file $filename for reading");

          $first_line = <$SRC>;
          chomp ($first_line);

          close ($SRC);

          gp_message ("debug", $subr_name, "first_line = $first_line");

          if ($first_line =~ /^Source\s+file:\s+([^\s]+)/)
            {
              $src_file = $1
            }
          else
            {
              $src_file = "";
            }
        }
    }
  elsif ($from_where eq "disassembly")
    {
      $msg = "unable to open disassembly file $filename for reading:";
      open ($DIS, "<", $filename)
        or die ($subr_name . " - " . $msg . " " . $!);
      gp_message ("debug", $subr_name, "opened file $filename for reading");

      $file_is_empty = is_file_empty ($filename);

      if ($file_is_empty)
#------------------------------------------------------------------------------
# Currently, the disassembly file for <static> functions appears to be empty
# on aarch64.  This might be a bug, but it is in any case better to handle
# this situation.
#------------------------------------------------------------------------------
        {
          $first_line = "";
          $msg = "file $filename is empty";
          gp_message ("debugM", $subr_name, $msg);
        }
      else
        {
          $first_line = <$DIS>;
        }

      close ($DIS);

      if ($first_line =~ /^Source\s+file:\s+([^\s]+)/)
        {
          $src_file = "$1"
        }
      else
        {
          $src_file = "";
        }
    }

  if (length ($routine))
    {
      $the_title .= " $routine";
    }

  if (length ($src_file))
    {
      if ($src_file ne "(unknown)")
        {
          $the_title .= " ($src_file)";
        }
      else
        {
          $the_title .= " $src_file";
        }
    }

  return ($the_title);

} #-- End of subroutine set_title

#------------------------------------------------------------------------------
# Handles where the output should go.  If needed, a directory to store the
# results in is created.
#------------------------------------------------------------------------------
sub set_up_output_directory
{
  my $subr_name = get_my_name ();

  my $error_code;
  my $msg;
  my $mkdir_output_msg;
  my $outputdir = "does_not_exist_yet";
  my $rm_output_msg;
  my $success;
  my $target_cmd;

  my $define_new_output_dir = $g_user_settings{"output"}{"defined"};
  my $overwrite_output_dir  = $g_user_settings{"overwrite"}{"defined"};

  if ((not $define_new_output_dir) and (not $overwrite_output_dir))
#------------------------------------------------------------------------------
# If neither -o or -O are set, find the next number to be used in the name for
# the default output directory.
#------------------------------------------------------------------------------
    {
      my $dir_id = 1;
      while (-d "display.".$dir_id.".html")
        { $dir_id++; }
      $outputdir = "display.".$dir_id.".html";
    }
  elsif ($define_new_output_dir)
#------------------------------------------------------------------------------
# The output directory is defined with the -o option.
#------------------------------------------------------------------------------
    {
      $outputdir = $g_user_settings{"output"}{"current_value"};
    }
  elsif ($overwrite_output_dir)
#------------------------------------------------------------------------------
# The output directory is defined with the -O option.
#------------------------------------------------------------------------------
    {
      $outputdir = $g_user_settings{"overwrite"}{"current_value"};
    }

#------------------------------------------------------------------------------
# The name of the output directory is known and we can proceed.
#------------------------------------------------------------------------------
  $msg = "the target output directory is $outputdir";
  gp_message ("debug", $subr_name, $msg);

  if (-d $outputdir)
    {
#------------------------------------------------------------------------------
# The -o option is used, but the directory already exists.
#------------------------------------------------------------------------------
      if ($define_new_output_dir)
        {
          $msg  = "directory $outputdir already exists";
          gp_message ("error", $subr_name, $msg);
          $msg  = "use the -O/--overwite  option to overwrite an";
          $msg .= " existing directory";
          gp_message ("error", $subr_name, $msg);

          $g_total_error_count++;

          gp_message ("abort", $subr_name, $g_abort_msg);

        }
      elsif ($overwrite_output_dir)
#------------------------------------------------------------------------------
# It is a bit risky to remove this directory and so we proceed with caution.
# What if the user decides to call it "*" e.g. "-O \*" for example? While this
# should have been caught when processing the options, we still like to
# be very cautious here before executing /bin/rm -rf.
#------------------------------------------------------------------------------
        {
          if ($outputdir eq "*")
            {
              $msg = "it is not allowed to use * as a value for the -O option";
              gp_message ("error", $subr_name, $msg);

              $g_total_error_count++;

              gp_message ("abort", $subr_name, $g_abort_msg);
            }
          else
            {
#------------------------------------------------------------------------------
# The output directory exists, but it is okay to overwrite it. It is
# removed here and created again below.
#------------------------------------------------------------------------------
              $target_cmd = $g_mapped_cmds{"rm"} . " -rf " . $outputdir;
              ($error_code, $rm_output_msg) = execute_system_cmd ($target_cmd);

                if ($error_code != 0)
                  {
                    $msg = "fatal error when trying to remove $outputdir";
                    gp_message ("error", $subr_name, $rm_output_msg);
                    gp_message ("error", $subr_name, $msg);

                    $g_total_error_count++;

                    gp_message ("abort", $subr_name, $g_abort_msg);
                  }
                else
                  {
                    $msg = "directory $outputdir has been removed";
                    gp_message ("debug", $subr_name, $msg);
                  }
            }
        }
    } #-- End of if-check for $outputdir

#------------------------------------------------------------------------------
# When we get here, the fatal scenarios have not occurred and the name for
# $outputdir is known.  Time to create it.  Note that recursive creation is
# supported and the user umask settings control the access permissions.
#------------------------------------------------------------------------------
  $target_cmd = $g_mapped_cmds{"mkdir"} . " -p " . $outputdir;
  ($error_code, $mkdir_output_msg) = execute_system_cmd ($target_cmd);

  if ($error_code != 0)
    {
      $msg = "a fatal problem occurred when creating directory $outputdir";
      gp_message ("error", $subr_name, $mkdir_output_msg);
      gp_message  ("error", $subr_name, $msg);

      $g_total_error_count++;

      gp_message ("abort", $subr_name, $g_abort_msg);
    }
  else
    {
      $msg = "created output directory $outputdir";
      gp_message  ("debug", $subr_name, $msg);
    }

  return ($outputdir);

} #-- End of subroutine set_up_output_directory

#------------------------------------------------------------------------------
# Split a line with function data into 3 components.
#------------------------------------------------------------------------------
sub split_function_data_line
{
  my $subr_name = get_my_name ();

  my ($input_line_ref) = @_;

  my $input_line = ${ $input_line_ref };

  my $decimal_separator = $g_locale_settings{"decimal_separator"};
  my $full_hex_address;
  my $function_name;
  my $hex_address;
  my $length_metric_list;
  my $length_remainder;
  my $length_target_string;
  my $list_with_metrics;
  my $marker;
  my $msg;
  my $reduced_line;
  my $remainder;
 
  my @hex_addresses = ();
  my @special_marker = ();
  my @the_function_name = ();

  my $find_hex_address_regex = '\s*(\d+:0x[a-fA-F0-9]+)\s+(.*)';
  my $find_marker_regex = '(^\*).*';
  my $find_metrics_1_regex  = '\)*\ +([0-9,' . $decimal_separator;
     $find_metrics_1_regex .= '\ ]*$)';
  my $find_metrics_2_regex  = '\)*\ +\[.+\]\s+([0-9,' . $decimal_separator;
     $find_metrics_2_regex  = '\ ]*$)';
  my $get_hex_address_regex = '(\d+):0x(\S+)';

  $reduced_line = $input_line;

  if ($input_line =~ /$find_hex_address_regex/)
    {
      if (defined ($1) )
        {
          $full_hex_address = $1;
          $reduced_line =~ s/$full_hex_address//;

          $msg = "full_hex_address = " . $full_hex_address;
          gp_message ("debugXL", $subr_name, $msg);
          $msg = "reduced_line = " . $reduced_line;
          gp_message ("debugXL", $subr_name, $msg);
        }
      if (defined ($2) )
        {
          $remainder = $2;
          $msg = "remainder = " . $remainder;
          gp_message ("debugXL", $subr_name, $msg);

          if (($remainder =~ /$find_metrics_1_regex/) or
              ($remainder =~ /$find_metrics_2_regex/))
            {
              if (defined ($1))
                {
                  $list_with_metrics = $1;
                  $msg = "before list_with_metrics = " . $list_with_metrics;
                  gp_message ("debugXL", $subr_name, $msg);

                  $list_with_metrics =~ s/$g_rm_surrounding_spaces_regex//g;
                  $msg = "after list_with_metrics = " . $list_with_metrics;
                  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# Remove the function name from the string.
#------------------------------------------------------------------------------
                  $length_remainder   = length ($remainder);
                  $length_metric_list = length ($list_with_metrics);

                  $msg = "length remainder = " . $length_remainder;
                  gp_message ("debugXL", $subr_name, $msg);

                  $msg = "length list_with_metrics = " . $length_metric_list;
                  gp_message ("debugXL", $subr_name, $msg);

                  $length_target_string = $length_remainder -
                                          $length_metric_list - 1;
                  $function_name = substr ($remainder, 0,
                                           $length_target_string, '');

                  $msg = "new function_name  = " . $function_name;
                  gp_message ("debugXL", $subr_name, $msg);

                  $reduced_line = $function_name;
                  $reduced_line =~ s/$g_rm_surrounding_spaces_regex//g;

                  $msg = "reduced_line = " . $reduced_line;
                  gp_message ("debugXL", $subr_name, $msg);

#------------------------------------------------------------------------------
# In some lines, the function name has a "*" prepended.  Isolate this marker
# and later on remove it from the function name.
# TBD: Can probably be done more efficiently.
#------------------------------------------------------------------------------
                  if ($reduced_line =~ /$find_marker_regex/)
                    {
                      if (defined ($1))
                        {
                          $marker = $1;
                          $msg = "found the marker = " . $marker;
                          gp_message ("debugXL", $subr_name, $msg);
                        }
                      else
                        {
                          $msg  = "first character in " . $reduced_line ;
                          $msg .= " is not expected";
                          gp_message ("assertion", $subr_name, $msg);
                        }
                    }
                  else
                    {
                          $marker = "X";
                    }
                }
              else
                {
                  $msg  = "failure to find metric values following the ";
                  $msg .= "function name";
                  gp_message ("assertion", $subr_name, $msg);
                }
            }
          else
            {
              $msg = "cannot find metric values in remainder";
              gp_message ("debugXL", $subr_name, $msg);
              gp_message ("assertion", $subr_name, $msg);
            }
        }
#------------------------------------------------------------------------------
# We now have the 3 main objects from the input line.  Next, they are processed
# and stored.
#------------------------------------------------------------------------------
      if ($full_hex_address =~ /$get_hex_address_regex/)
        {
          if (defined ($1) and defined ($2))
            {
              $hex_address = "0x" . $2;
              push (@hex_addresses, $full_hex_address);

              $msg = "pushed full_hex_address = " . $full_hex_address;
              gp_message ("debugXL", $subr_name, $msg);
            }
        }
      else
        {
          $msg = "full_hex_address = $full_hex_address has an unknown format";
          gp_message ("assertion", $subr_name, $msg);
        }
      if ($marker eq "*")
        {
          push (@special_marker, "*");
        }
      else
        {
          push (@special_marker, "X");
        }

      $reduced_line =~ s/^\*//;

      $msg = "RESULT full_hex_address = " . $full_hex_address;
      $msg .= " -- metric values = " . $list_with_metrics;
      $msg .= " -- marker = " . $marker;
      $msg .= " -- function name = " . $reduced_line;
      gp_message ("debugXL", $subr_name, $msg);
    }

  return (\$full_hex_address, \$marker, \$reduced_line, \$list_with_metrics);

} #-- End of subroutine split_function_data_line

#------------------------------------------------------------------------------
# Routine to generate webfriendly names
#------------------------------------------------------------------------------
sub tag_name
{
  my $subr_name = get_my_name ();

  my ($target_name) = @_;

#------------------------------------------------------------------------------
# Keeps track how many names have been tagged already.
#------------------------------------------------------------------------------
  state $S_total_tagged_names = 0;

  my $msg;
  my $unique_name;

  gp_message ("debug", $subr_name, "target_name on entry  = $target_name");

#------------------------------------------------------------------------------
# Undo conversion of < in to &lt;
#------------------------------------------------------------------------------

#------------------------------------------------------------------------------
# TBD: Legacy - What is going on here and is this really needed?!
# We need to replace the "<" symbol in the code by "&lt;".
#------------------------------------------------------------------------------
  $target_name =~ s/$g_html_less_than_regex/$g_less_than_regex/g;

#------------------------------------------------------------------------------
# Remove inlining info
#------------------------------------------------------------------------------
  $target_name =~ s/, instructions from source file.*//;

  if (defined $g_tagged_names{$target_name})
    {
      $msg  = "target_name = $target_name is already defined: ";
      $msg .= $g_tagged_names{$target_name};
      gp_message ("debug", $subr_name, $msg);

      $msg = "target_name on return = $target_name";
      gp_message ("debug", $subr_name, $msg);

      return ($g_tagged_names{$target_name});
    }
  else
    {
      $unique_name = "ftag".$S_total_tagged_names;
      $S_total_tagged_names++;
      $g_tagged_names{$target_name} = $unique_name;

      $msg  = "target_name = $target_name is new and added: ";
      $msg .= "g_tagged_names{$target_name} = $g_tagged_names{$target_name}";
      gp_message ("debug", $subr_name, $msg);

      $msg = "target_name on return = $target_name";
      gp_message ("debug", $subr_name, $msg);

      return ($unique_name);
    }

} #-- End of subroutine tag_name

#------------------------------------------------------------------------------
# Generate a string to terminate the HTML document.
#------------------------------------------------------------------------------
sub terminate_html_document
{
  my $subr_name = get_my_name ();

  my $html_line;

  $html_line  = "</body>\n";
  $html_line .= "</html>";

  return (\$html_line);

} #-- End of subroutine terminate_html_document

#------------------------------------------------------------------------------
# Perform some basic checks to ensure the input data is consistent.  This part
# could be refined and expanded over time.  For example by using a checksum
# mechanism to verify the consistency of the executables.
#------------------------------------------------------------------------------
sub verify_consistency_experiments
{
  my $subr_name = get_my_name ();

  my ($exp_dir_list_ref) = @_;

  my @exp_dir_list    = @{ $exp_dir_list_ref };

  my $executable_name;
  my $full_path_executable_name;
  my $msg;
  my $ref_executable_name;

  my $first_exp_dir     = $TRUE;
  my $count_differences = 0;

#------------------------------------------------------------------------------
# Enforce that the full path names to the executable are the same.  This could
# be overkill and a checksum approach would be more flexible.
#------------------------------------------------------------------------------
  for my $full_exp_dir (@exp_dir_list)
    {
      my $exp_dir = get_basename ($full_exp_dir);
      gp_message ("debug", $subr_name, "exp_dir = $exp_dir");
      if ($first_exp_dir)
        {
          $first_exp_dir = $FALSE;
          $ref_executable_name =
			$g_exp_dir_meta_data{$exp_dir}{"full_path_exec"};
          $msg = "ref_executable_name = " . $ref_executable_name;
          gp_message ("debug", $subr_name, $msg);
          next;
        }
        $full_path_executable_name =
			$g_exp_dir_meta_data{$exp_dir}{"full_path_exec"};
        $msg = "full_path_executable_name = " . $full_path_executable_name;
        gp_message ("debug", $subr_name, $msg);

        if ($full_path_executable_name ne $ref_executable_name)
          {
            $count_differences++;
            $msg  = $full_path_executable_name . " does not match";
            $msg .= " " . $ref_executable_name;
            gp_message ("debug", $subr_name, $msg);
          }
    }

  $executable_name = get_basename ($ref_executable_name);

  return ($count_differences, $executable_name);

} #-- End of subroutine verify_consistency_experiments

#------------------------------------------------------------------------------
# Check if the input item is valid for the data type specified. Validity is
# verified in the context of gprofng.  The definition for the metrics is a
# good example of that.
#------------------------------------------------------------------------------
sub verify_if_input_is_valid
{
  my $subr_name = get_my_name ();

  my ($input_item, $data_type) = @_;

  my $msg;
  my $return_value = $FALSE;

#------------------------------------------------------------------------------
# These value are allowed to be case insensitive, so we convert to lower
# case first.
#------------------------------------------------------------------------------
  if (($data_type eq "onoff") or ($data_type eq "size"))
    {
      $input_item = lc ($input_item);
    }

  if ($data_type eq "metrics")
#------------------------------------------------------------------------------
# A gprofng metric definition.  Either consists of "default" only, or starts
# with e or i, followed by one or more from the set {.,%,!,+} and a keyword.
# This pattern may be repeated with a ":" as the separator.
#------------------------------------------------------------------------------
    {
      my @metric_list = split (":", $input_item);

#------------------------------------------------------------------------------
# Check if the pattern is valid.  If not, bail out and return $FALSE.
#------------------------------------------------------------------------------
      for my $metric (@metric_list)
        {
          if ($metric =~ /^default$|^[ei]*[\.%\!\+]+[a-z]*$/)
            {
              $return_value = $TRUE;
            }
          else
            {
              $return_value = $FALSE;
              last;
            }
        }
    }
  elsif ($data_type eq "metric_names")
#------------------------------------------------------------------------------
# A gprofng metric definition but without the flavour and visibility .  Either
# the name consists of "default" only, or a keyword with lowercase letters
# only.  This pattern may be repeated with a ":" as the separator.
#------------------------------------------------------------------------------
    {
      my @metric_list = split (":", $input_item);

#------------------------------------------------------------------------------
# Check if the pattern is valid.  If not, bail out and return $FALSE.
#------------------------------------------------------------------------------
      for my $metric (@metric_list)
        {
          if ($metric =~ /^default$|^[a-z]*$/)
            {
              $return_value = $TRUE;
            }
          else
            {
              $return_value = $FALSE;
              last;
            }
        }
    }
  elsif ($data_type eq "path")
#------------------------------------------------------------------------------
# This can be almost anything, including "/" and "."
#------------------------------------------------------------------------------
    {
      if ($input_item =~ /^[\w\/\.\-]*$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "boolean")
    {
#------------------------------------------------------------------------------
# This is TRUE (=1) or FALSE (0).
#------------------------------------------------------------------------------
      if ($input_item =~ /^[01]$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "onoff")
#------------------------------------------------------------------------------
# This is either "on" OR "off".
#------------------------------------------------------------------------------
    {
      if ($input_item =~ /^on$|^off$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "size")
#------------------------------------------------------------------------------
# Supported values are "on", "off", "s", "m", "l", or "xl".
#------------------------------------------------------------------------------
    {
      if ($input_item =~ /^on$|^off$|^s$|^m$|^l$|^xl$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "pinteger")
#------------------------------------------------------------------------------
# This is a positive integer.
#------------------------------------------------------------------------------
    {
      if ($input_item =~ /^\d*$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "integer")
#------------------------------------------------------------------------------
# This is a positive or negative integer.
#------------------------------------------------------------------------------
    {
      if ($input_item =~ /^\-?\d*$/)
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "pfloat")
#------------------------------------------------------------------------------
# This is a positive floating point number, but we accept a positive integer
# number as well.
#
# TBD: Note that we use the "." here. Maybe should support a "," too.
#------------------------------------------------------------------------------
    {
      if (($input_item =~ /^\d*\.\d*$/) or ($input_item =~ /^\d*$/))
        {
          $return_value = $TRUE;
        }
    }
  elsif ($data_type eq "float")
#------------------------------------------------------------------------------
# This is a positive or negative floating point number, but we accept an
# integer number as well.
#
# TBD: Note that we use the "." here. Maybe should support a "," too.
#------------------------------------------------------------------------------
    {
      if (($input_item =~ /^\-?\d*\.\d*$/) or ($input_item =~ /^\-?\d*$/))
        {
          $return_value = $TRUE;
        }
    }
  else
    {
      $msg = "the $data_type data type for input $input_item is not supported";
      gp_message ("assertion", $subr_name, $msg);
    }

  return ($return_value);

} #-- End of subroutine verify_if_input_is_valid

#------------------------------------------------------------------------------
# Scan the leftovers in ARGV.  Other than the option generated by the driver,
# this list should be empty.  Anything left here is considered to be a fatal
# error and pushed into the g_error_msgs buffer.
#
# We use two different arrays for the errors found.  This allows us to group
# the same type of errors.
#------------------------------------------------------------------------------
sub wrap_up_user_options
{
  my $subr_name = get_my_name ();

  my @opt_unsupported = ();
  my @opt_ignored     = ();

  my $current_option;
  my $driver_inserted = "--whoami=gprofng display html";
  my $ignore_option;
  my $msg;
  my $option_delimiter = "--";

  if (@ARGV)
    {
      $msg = "items in ARGV: " . join (" ", @ARGV);
      gp_message ("debugXL", $subr_name, $msg);

      $ignore_option = $FALSE;
      for my $i (keys @ARGV)
        {
          $current_option = $ARGV[$i];

          $msg = "ARGV[$i] = $current_option";

          if ($current_option eq $option_delimiter)
#------------------------------------------------------------------------------
# The user may use a feature of GetOptions to delimit the options.  After
# this, only experiment names are allowed and these have been handled already,
# so anything found after this delimite is an error.
#
# This is why we set a flag if the delimiter has been found.
#------------------------------------------------------------------------------
            {
              $ignore_option = $TRUE;
              gp_message ("debugXL", $subr_name, $msg . " (option delimiter)");
            }
          elsif ($ignore_option)
#------------------------------------------------------------------------------
# We have seen the delimiter, but there are still options, or other strings.
# In any case, it is not allowed.
#------------------------------------------------------------------------------
            {
              push (@opt_ignored, $current_option);
              gp_message ("debugXL", $subr_name, $msg . " (ignored)");
            }
          elsif ($current_option ne $driver_inserted)
#------------------------------------------------------------------------------
# The gprofng driver inserts this and it should be ignored.  This is why we
# only recorded those options different than the one inserted by the driver.
#------------------------------------------------------------------------------
            {
              push (@opt_unsupported, $current_option);
              gp_message ("debugXL", $subr_name, $msg . " (unsupported)");
            }
          else
#------------------------------------------------------------------------------
# The gprofng driver inserts this option and it should be ignored.
#------------------------------------------------------------------------------
            {
              gp_message ("debugXL", $subr_name, $msg .
                          " (driver inserted and ignored)");
            }
        }
    }

#------------------------------------------------------------------------------
# Store any illegal input in the g_error_msgs buffer.
#------------------------------------------------------------------------------
  if (@opt_ignored)
    {
      $msg = "the following input is out of place:";
      for my $i (keys @opt_ignored)
        {
          $msg .= " " . $opt_ignored[$i];
        }
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }
  if (@opt_unsupported)
    {
      $msg = "the following items in the input are not supported:";
      for my $i (keys @opt_unsupported)
        {
          $msg .= " " . $opt_unsupported[$i];
        }
      gp_message ("error", $subr_name, $msg);

      $msg  = "perhaps an error in the option name, or an option value";
      $msg .= " is missing?";
      gp_message ("error", $subr_name, $msg);

      $g_total_error_count++;
    }

  return (0);

} #-- End of subroutine wrap_up_user_options
